Robert McPythons
Robert McPythons

Reputation: 174

Mock / create_autospec: verify argument types

In a unit test with mocked function, I would like to verify not only the count of arguments, but also that those conform the declared types of autospec-mocked function.

Here is the example code:

from unittest.mock import create_autospec


def foo(a: int, b: str):
    return str(a) + b

mock_foo = create_autospec(foo)

foo = mock_foo  # would be patched by e.g. @patch or monkeypatch fixture
foo(1, 2)
assert foo.called 

This code tests that foo is called correctly. e.g. such call would fail: foo(1,2,3) This includes validation of arguments, but not of their types. Is there a way for autospec or another function to validate the types of passed arguments? This information is available in the signature of the method, so I imagine I could write myself such a validation using inspect module. But is there something standard, a built-in or library?

Upvotes: 1

Views: 989

Answers (1)

sherbang
sherbang

Reputation: 16215

This can be solved by adding a side_effect to your mock that compares arguments and annotations:

from unittest.mock import create_autospec

def check_params(method):
    def cp(*args, **kwargs):
        for arg, typ in zip(args, method.__annotations__.values()):
            assert isinstance(arg, typ)
        for k, v in kwargs.values():
            assert isinstance(v, method.__annotations__[k])
    return cp

def foo(a: int, b: str):
    return str(a) + b

mock_foo = create_autospec(foo)
mock_foo.side_effect=check_params(foo)

foo = mock_foo  # would be patched by e.g. @patch or monkeypatch fixture
foo(1, 2)
assert foo.called 

This will raise an AssertionError because b should be a str but is an int.

This could also be used with a class: from unittest.mock import create_autospec

def check_params(method):
    def cp(*args, **kwargs):
        for arg, typ in zip(args, method.__annotations__.values()):
            assert isinstance(arg, typ)
        for k, v in kwargs.values():
            assert isinstance(v, method.__annotations__[k])
    return cp

class Foo:
    def foo(a: int, b: str):
        return str(a) + b

mock_foo = create_autospec(Foo)
mock_foo.foo.side_effect=check_params(Foo.foo)

mock_foo.foo(1, 2)
assert mock_foo.foo.called

Upvotes: 0

Related Questions