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 ==========================