pythondjangounit-testingmockingpython-mock

Accessing Calls to Mocked Class functions


I have written a custom class to mock a general API client in a codebase so that I can centrally and easily mock all of the class methods for unit testing. This is working great so far, however I am looking for a way to track individual calls to each class method. Right now that only trackable call via Mock is the initial class instantiation.

Here is the mock class:

from faker import Factory

faker = Factory.create()

class MockAPIClass
  def get_some_data(self, data):
    return f"{data} - {faker.pyint()}"

Then in my util file:

def func_to_test_that_calls_client(arg):
  client = regular_api_client()
  return client.get_some_data(arg)

Then in my unit tests:

from unittest import mock
from django.test import TransactionTestCase
from .file import MockAPIClass

from .util import func_to_test_that_calls_client

class TestUils(TransactionTestCase):

  def setUp(self):
    self.api_call_patcher = mock.patch('path.to.mocked.class.instantiation')
    self.patch_api = self.api_call_patcher.start()
    self.mock_api = MockAPIClass()  # this done so that the mocked class can be referenced below
    self.patch_api.return_value = self.mock_api

  def tearDown(self):
    mock.patch.stopall()

  def test_util_func(self):
    res = func_to_test_that_calls_client("test")

    self.assertTrue(res)
    self.patch_api.assert_called_once()
  

The above functions exactly as expected and intended. However, inside of the funciton func_to_test_that_calls_client, the original client is instantiated then the class method get_some_data() is called. With this implementation, I have no visibility into the call stack of the class methods like that function, only the parent instantiation of the class. I would like to be able to see for example that func_to_test_that_calls_client was called with "test" with this current implementation. Is there a way to do this with mock or some other python trick?


Solution

  • A standard Mock object (which is what patch uses by default) works fine. There's no need to build your own special mock class.

    Using this as a simulated version of your util.py (I needed to add a dummy regular_api_client to have a target for patch):

    regular_api_client = lambda: None
    
    def func_to_test_that_calls_client(arg):
      client = regular_api_client()
      return client.get_some_data(arg)
    

    this simple test seems to do what you're after:

    from unittest.mock import patch
    
    from util import func_to_test_that_calls_client
    
    def test_util_func():
        with patch('util.regular_api_client') as mock_client_class:
            mock_client = mock_client_class()
            func_to_test_that_calls_client("test")
            mock_client.get_some_data.assert_called_with("test")
    

    Note that you can validate that the above test works (I always second-guess myself with mock methods on whether I actually called a "real" mock method or another mock, heh) by changing assert_called_with("test") to assert_called_with("something else") and seeing that the test now fails:

    E           AssertionError: expected call not found.
    E           Expected: get_some_data('something else')
    E           Actual: get_some_data('test')