Accessing Calls to Mocked Class functions

Question:

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?

Asked By: Wold

||

Answers:

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')
Answered By: Samwise