pythonunit-testingpython-unittestpython-mock

Pass parameter to side_effect function for patching in unittest.mock


I'm using patch from unittest.mock to change the behavior of a remote API call in my test.

I have three different functions that return three different json files that represent the mock data to be returned from the API. For each mock api call, I am setting side_effect to be one of these functions. This pattern isn't DRY, but I don't know how to pass a parameter to a side_effect function.

The three mock api call functions look like this:

def mock_api_call_1():
    with open('path/to/mock_1.json') as f:
        mock_data = json.load(f)
    return mock_data

Here's my test

class MyTest(TestCase):

    def test_api(self):

        with patch('mymodule.utils.api_call', side_effect=mock_api_call_1):
            do_crud_operations()
            self.assertEqual(MyModel.objects.all().count(), 10)

        with patch('mymodule.utils.api_call', side_effect=mock_api_call_2):
            do_crud_operations()
            self.assertEqual(MyModel.objects.all().count(), 11)

How can I refactor this code to be able to pass a parameter to the side_effect (mock_call(1) instead of mock_call_1).

From the unittest docs, I see that:

side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value.

I see that the function passed to side_effect takes the same arguments as the mock, but I'm still not sure how best to use mock to accomplish this. I'll eventually want to add more test cases, so I don't want to be hard-coding different mock_api_call functions.


Solution

  • Use a lambda function:

    from unittest import TestCase, main
    from unittest.mock import Mock, patch
    import sys
    
    def mock_api_call(x):
        print(x)
    
    class MyTest(TestCase):
        def test_api(self):
            with patch('sys.exit',
                        side_effect=lambda x: mock_api_call(x)) as m:
                m(0)
                sys.exit(0)
    
                m(1)
                sys.exit(1)
    
    
    if __name__ == '__main__':
        main()