Search code examples
unit-testingmockingpython-unittestpython-unittest.mock

Mock a function with parameters that is indirectly called with a mock function instead


I have been trying to test a function that calls another function with some parameters. I am trying to mock the latest so that it won't actually run and instead executes a mock function that returns some mock values.

What I have -simplified- looks like that:

def function_to_test():
    a = 2
    b = 3
    c = 4
    results = second_function(a, b, c)
    return results

Then the function that I am trying to mock looks like that:

def second_function(a, b , c):
    a = b + c
    return a 

Both function_to_test and second_function belong to the class Example.

I am using unittest for my tests and I cannot switch to pytest unfortunatelly, so no pytest options are helpful.

What I have managed to do so far with the test is:

@patch('rootfolder.subfolder.filename.Example.second_function', autospec=True)
def test_function_to_test(self, get_content_mock):
    get_content_mock.return_value = mocked_second_function()

    res = function_to_test()
    self.assertEqual(res, 10)

As you can see I am trying to use a mocked function instead the actual second_function that looks like that:

def mocked_second_function(a, b, c):
    # using a, b, c for other actions
    # for the question I will just print them but they are actually needed
    print(f"{a}, {b}, {c}")
    return 10

The problem is that when I set the get_content_mock.return_value = mocked_second_function().

I am required to pass the parameters, but in my actual problem, these parameters are being generated at the function_to_test so I have no way of knowing them beforehand.

I read many related questions and documentation but I cannot seem to find something that helps my problem. Any help or even a different approach would be helpful.


Solution

  • Using the unittest library there is assert_called_with. If this is used with the MagicMock capability then you can test the first_function without the actual second_function being ran but test that it has been called with the correct parameters.

    Here is an example test with the correct and the wrong parameters:

    import unittest
    from unittest.mock import MagicMock
    
    class Example:
        def first_function(self):
            a = 2
            b = 3
            c = 4
            results = self.second_function(a, b, c)
            return results
    
        def second_function(self, a, b , c):
            a = b + c
            return a
    
    class TestMockSecondMethod(unittest.TestCase):
    
        def test_func_dut_pass(self):
            example = Example()
            example.second_function = MagicMock(return_value=7)
            result = example.first_function()
            example.second_function.assert_called_with(2, 3, 4)
            self.assertEqual(7, result)
    
        def test_func_dut_fail(self):
            example = Example()
            example.second_function = MagicMock(return_value=7)
            result = example.first_function()
            example.second_function.assert_called_with(1, 3, 4)
            self.assertEqual(7, result)
    
    
    if __name__ == '__main__':
        unittest.main()
    

    This gave the following output:

    $ python3.6 -m unittest --v so_unittest_mock.py 
    test_func_dut_fail (so_unittest_mock.TestMockSecondMethod) ... FAIL
    test_func_dut_pass (so_unittest_mock.TestMockSecondMethod) ... ok
    
    ======================================================================
    FAIL: test_func_dut_fail (so_unittest_mock.TestMockSecondMethod)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/home/user1/so_unittest_mock.py", line 28, in test_func_dut_fail
        example.second_function.assert_called_with(1, 3, 4)
      File "/usr/lib/python3.6/unittest/mock.py", line 814, in assert_called_with
        raise AssertionError(_error_message()) from cause
    AssertionError: Expected call: mock(1, 3, 4)
    Actual call: mock(2, 3, 4)
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.003s
    
    FAILED (failures=1)