python: unit test with fixture and patch decorators
Tags: python, unittest, fixture, mock
Published: 2023-03-08 02:57:41

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 

```shell
src/
    ├── my.py  
    ├── my_modules/
    │   ├── __init__.py
    │   └── util.py
    └── tests/
        ├── __init__.py
        ├── test_my.py
        ├── test_unit.py
        └── conftest.py
```
my.py
```python
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)
```

util.py

```python
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)
```

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

```python
import pytest

@pytest.fixture
def list_mock():
    return ["input1", "input2"]
```

## Add unit test for get_data

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

```python
@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
```

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

```python
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
```

## Summary

I defined ```list[str]``` 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.