Since I always seem to search for these examples over and over again, here it is, basic mocking with pytest.
My example code is this (testmodule.py):
import time
def testfunc():
print("In testfunc, sleeping")
time.sleep(5)
return 5
if __name__ == "__main__":
testfunc()
It can be run:
$ python3 testmodule.py In testfunc, sleeping $
Not surprisingly, running it takes about five seconds. The time.sleep() call simulates some long-running operation in the code.
A test for testfunc() is something like this (test_testfunc.py):
import testmodule
def test_testfunc():
assert testmodule.testfunc() == 5
Running it with pytest:
$ pytest ========================= test session starts ========================== platform linux -- Python 3.7.3, pytest-7.1.2, pluggy-1.0.0 rootdir: /home/markku/temp/pytest-mock-venv collected 1 item test_testfunc.py . [100%] ========================== 1 passed in 5.55s ===========================
Again not a surprise that the test takes over five seconds to run as the test really runs testfunc() as-is.
Classic example of mocking would be to replace the time-consuming function call (in this case time.sleep()) with a mock.
With pytest it can be done by first installing the pytest-mock plugin in the venv:
$ pip install pytest-mock ... Successfully installed pytest-mock-3.8.2 $
Then this is how mocking the time.sleep() call looks like (test_testfunc.py):
import testmodule
def test_testfunc(mocker):
mocker.patch("time.sleep")
assert testmodule.testfunc() == 5
The changes are the mocker argument (called a fixture in pytest context) to the test function and calling mocker.patch() to replace the expensive function call with a blank mock. Running the test again:
$ pytest ========================= test session starts ========================== platform linux -- Python 3.7.3, pytest-7.1.2, pluggy-1.0.0 rootdir: /home/markku/temp/pytest-mock-venv plugins: mock-3.8.2 collected 1 item test_testfunc.py . [100%] ========================== 1 passed in 0.55s ===========================
The test was now fast because the expensive function call was mocked out.
If the mocked function call would need to return something, it would look like this: mocker.patch("module.function", return_value=123)
Another case for mocking: Mocking some object methods. Here is another example code (testmodule2.py)
import time
class RunnerClass:
def __init__(self, number):
self.number = number
def run(self):
print("Running")
time.sleep(self.number)
return self.number
def testfunc2(runner):
print("Let's run the runner")
return runner.run()
if __name__ == "__main__":
runner = RunnerClass(5)
testfunc2(runner)
Running it:
$ python testmodule2.py Let's run the runner Running $
Now write a simple test to verify that testfunc2() returns the defined amount of seconds (test_testfunc2.py):
import testmodule2
def test_testfunc2():
runner = testmodule2.RunnerClass(5)
assert testmodule2.testfunc2(runner) == 5
Running the tests:
$ pytest ========================= test session starts ========================= platform linux -- Python 3.7.3, pytest-7.1.2, pluggy-1.0.0 rootdir: /home/markku/temp/pytest-mock-venv plugins: mock-3.8.2 collected 2 items test_testfunc.py . [ 50%] test_testfunc2.py . [100%] ========================== 2 passed in 5.55s ==========================
Success, but again the test started consuming time, so let’s create a mock that replaces RunnerClass and its run() method:
import testmodule2
def test_testfunc2(mocker):
mock_runner = mocker.MagicMock()
mock_runner.run.return_value = 4
assert testmodule2.testfunc2(mock_runner) == 4
mock_runner.run.assert_called_once()
And the results of running the tests:
$ pytest ========================= test session starts ========================= platform linux -- Python 3.7.3, pytest-7.1.2, pluggy-1.0.0 rootdir: /home/markku/temp/pytest-mock-venv plugins: mock-3.8.2 collected 2 items test_testfunc.py . [ 50%] test_testfunc2.py . [100%] ========================== 2 passed in 0.56s ==========================