New classes for unittest.mock

These ideas stemmed from my help thread here: Assert_called_with matching a type.

Anyways, I propose two new classes for unittest.mock:

class IsInstance:
    def __init__(self, typ: Type | Tuple[Type, ...]) -> None:
        self._typ = typ

    def __eq__(self, other : Any) -> bool:
        return isinstance(other, self._typ)

this directly answers the question in my linked help thread. It allows an easy way to verify a param is of a specific type.

Example usage:

mock = MagicMock()
mock.func(1)

# assert that the first param was an int
mock.func.assert_called_once_with(IsInstance(int))

Another (though related) proposal is the Verifier class:

class Verifier:
    def __init__(self, function: Callable[[Any], bool]):
        self._function = function

    def __eq__(self, other: Any) -> bool:
        return self._function(other)

This class allows you to run a specific function to verify a given parameter of a mock call.

Example usage:

mock = MagicMock()
mock.func(1)

def less_than_10(x):
    return x < 10

mock.func.assert_called_once_with(Verifier(less_than_10))

The definitions can have nice __repr__() methods added to make it a bit more readable on failure as well.

Open to thoughts, etc. Thanks folks.

2 Likes

I’ve implemented a version of the Verifier class in many test scripts, so +1 to its inclusion. I’ve never needed something like your IsInstance, however.

pytest wants to display LHS and RHS and highlight their diff on failure of assert A == B, where a custom repr may help, but I don’t think that applies when using mock.assert_called_with.

This is how it may be done currently:

import unittest
from unittest.mock import MagicMock

class Test(unittest.TestCase):
    def runTest(self):
        mock = MagicMock()
        mock.func(1)
        mock.func.assert_called_once()
        self.assertIsInstance(mock.func.call_args[0][0], int)
        self.assertTrue(mock.func.call_args[0][0] < 10)

While the proposal does simplify the existing approach, I wonder if the IsInstance test on a call argument is special or used often enough to justify it as a dedicated helper class. It seems to me that we should offer either a dedicated helper class for every equivalent assert* method, or just a Verifier helper class to minimize the burden of support with just a generalized class.

I could definitely see a world where we just add Verifier which makes it easy to use isinstance() via a lambda or similar to get the same behavior without needing IsInstance.

I guess I defer on further opinions.

For me it winds up being when I have a type that doesn’t define __eq__(). Previously I wind up using ANY or manually going through mock_calls then args/kwargs to find and check the specific argument I’m couldn’t easily check.

For me I would use IsInstance for cases where I just want to be sure the right type was passed (and not something like None). I would use Verifier for slightly more complex cases, probably alongside a lambda to verify short things like: “The arg was smaller than 5”.

I’ve also made a number of one off Verifier-like classes when the type does have __eq__(), but I only care about some of the values or a value might be hard to control. For example: an integration test where the database is automatically setting a created_at field. Most of the time I want to confirm that all the fields of the dataclass have the correct value, but am less interested in the exact value of the one datatime instance.

1 Like

These both fail against types that already know how to compare themselves against arbitrary types, preventing IsInstance.__eq__ or Verifier.__eq__ from being called at all.

Do you have an example? These follow very similar logic to ANY in unittest.mock today. I thought it does comparisons with the thing in the assert call on the correct side so it’s __eq__ gets called.

No :slight_smile: I think when I ran my test, I was careless about what got passed to mock.func and IsInstance, and interpreted the expected error as a false positive.

Maybe you’ll be interested in the dirty-equals library?

1 Like

That’s a cool library, but I’m not sure how it could emulate this type of behavior without having to call use mock_calls directly.

I think dirty_equals.IsInstance is exactly your proposed IsInstance class, while dirty_equals.FunctionCheck is exactly your proposed Verifier class, so the usage with mock.assert_called_once_with would be the same as that of your proposal.

1 Like

Oh wow you’re right. I can definitely use this where needed. I think there could be value in something like this being an included battery, but this library works well for me.

Thanks folks!