DEV Community

Kenichiro Nakamura
Kenichiro Nakamura

Posted on

python: unit test with fixture and patch decorators

In the [pervious article], I used multiple patch decorators to mock several functions. This time, I use Fixture with the decorators to see how they work together.

Fixture

When I have a reusable object across multiple unit tests, I can define a fixture and obtain it in the unit test function. I create one fixture this time to see how I can use it.

Structures and code

This is almost same as before, just modifying add_data and main method to take an argument, and

src/
    ├── my.py  
    ├── my_modules/
    │   ├── __init__.py
    │   └── util.py
    └── tests/
        ├── __init__.py
        ├── test_my.py
        ├── test_unit.py
        └── conftest.py
Enter fullscreen mode Exit fullscreen mode

my.py

import sys
from my_modules.util import util, get_data


def main(argv: list[str]):
    data = get_data(argv)
    return util(data)

if __name__ == '__main__':
    main(sys.argv)
Enter fullscreen mode Exit fullscreen mode

util.py

from datetime import datetime

def util(input: str) -> str:
    input = add_time(input)
    return f"util: {input}"

def add_time(input: str) -> str:
    return f"{datetime.now()}: {input}"

def get_data(input: list[str]) -> str:
    input = f"There are {len(input)} arguments"
    return add_time(input)
Enter fullscreen mode Exit fullscreen mode

Add fixture

I can define fixture in conftest.py that automatically recognize via test frameworks. I define a list_mock fixture this time.

conftest.py

import pytest

@pytest.fixture
def list_mock():
    return ["input1", "input2"]
Enter fullscreen mode Exit fullscreen mode

Add unit test for get_data

Let's add or modify the unit test code for get_data.

@patch('my_modules.util.add_time')
def test_get_data(add_time, list_mock):
    ct = datetime.now()
    expected = f"{ct}: There are {len(list_mock)} arguments"
    add_time.return_value = expected

    result = get_data(list_mock)

    assert expected == result
Enter fullscreen mode Exit fullscreen mode

I patch the add_time function and receive the mock as the first argument of the test function. Then, I receive the list_mock fixture as the second argument.

I can specify the fixture function name as argument name, then it is automatically passed to the function. Very easy!!

Update main unit tests

As the main method also requires list[str] argument, let's update them all. Just receive the list_mock fixture as an argument and pass it to the main function.

test_my.py

from my import main
from unittest.mock import patch, Mock

@patch("my.util", Mock(return_value="dummy"))
@patch("my.get_data", Mock(return_value="some data"))
def test_main(list_mock):
    result = main(list_mock)
    assert result =='dummy'

@patch("my.util")
@patch("my.get_data")
def test_main_util_called_with_expected_parameter(get_data_mock, util_mock, list_mock):
    get_data_mock.return_value = 'some data'
    util_mock.return_value = 'dummy'
    result = main(list_mock)
    assert result =='dummy'
    util_mock.assert_any_call('some data')

def test_main_util_called_with_expected_parameter_with(list_mock):
    with patch("my.util") as util_mock:
        util_mock.return_value = 'dummy'
        with patch("my.get_data") as get_data_mock:
            get_data_mock.return_value = 'some data'
            result = main(list_mock)
    assert result =='dummy'
    util_mock.assert_any_call('some data')


@patch("my.util", Mock(return_value="dummy"))
@patch("my.get_data")
def test_main_get_data_called(get_data_mock, list_mock):
    get_data_mock.return_value = 'some data'
    result = main(list_mock)
    assert result =='dummy'
    assert get_data_mock.called
Enter fullscreen mode Exit fullscreen mode

Summary

I defined


 as a fixture this time, but I can use any object including ``Mock`` object. When I write similar test objects in multiple unit test, we can move it to fixture.

See [the pytest fixtures official document](https://docs.pytest.org/en/7.1.x/how-to/fixtures.html) for more detail.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)