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)