DEV Community

Kenichiro Nakamura
Kenichiro Nakamura

Posted on

python: use multiple patch decorators to mock functions

I wrote how to mock in the previous article. This time, I mock multiple functions in the test to see how I can handle them.

Structures and code

This is almost same as before, just adding one more function in util.py.

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

my.py

from my_modules.util import util, get_data def main(): data = get_data() return util(data) if __name__ == '__main__': main() 
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() -> str: return "Return some data" 
Enter fullscreen mode Exit fullscreen mode

Add a unit test for main function

To test the main method in the my.py, I need to mock both util and get_data method. Let's do it.

Firstly, I added another patch and specify return_value. It doesn't change the test body though.

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

Let's receive the mock objects as the arguments.

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

The interesting part is the order of argument. As you see, I can get the mock from bottom up order of the patch decorators. I firstly though I can receive the mocks in the same order as the decorators, but I was wrong.

Finally, let's try with statement.

def test_main_util_called_with_expected_parameter_with(): 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() assert result =='dummy' util_mock.assert_any_call('some data') 
Enter fullscreen mode Exit fullscreen mode

I am not 100% sure if this is the correct way to implement, but it works as expected anyway.

I just added one more example. This time, I specify the Mock object in the first decorator only. In this case, I can receive just one mock object as an argument.

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

I check if get_data function is called.

Summary

I understand when I get the mock object as the arguments. It's a bit confusing but once I understand, it's very useful.

Top comments (0)