Basic Mocking with Pytest

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

Leave a Reply