Message 339812 - Python tracker

mock.Mock doesn't do signature validation by default for constructor and methods. This is expected. create_autospec [0] should be used to make sure the signature is validated.'

import dataclasses
import unittest.mock

@dataclasses.dataclass
class Foo:
    name: str
    baz: float
    bar: int = 12

FooMock = unittest.mock.create_autospec(Foo)
fooMock = FooMock()  # Will fail now since it's specced

➜  cpython git:(master) ./python.exe ../backups/bpo36580.py
Traceback (most recent call last):
  File "../backups/bpo36580.py", line 11, in <module>
    fooMock = FooMock()  # should fail: Foo.__init__ takes two arguments
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/mock.py", line 984, in __call__
    _mock_self._mock_check_sig(*args, **kwargs)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/mock.py", line 103, in checksig
    sig.bind(*args, **kwargs)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/inspect.py", line 3021, in bind
    return args[0]._bind(args[1:], kwargs)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/inspect.py", line 2936, in _bind
    raise TypeError(msg) from None
TypeError: missing a required argument: 'name'

On the other hand 'name' in dir(FooMock) doesn't have the attributes (name and baz) present I suppose they are constructed dynamically when an object is created from Foo since they are present in dir(Foo()) and mock is not able to detect them? mock.create_autospec does an initial pass of dir(Foo) to copy the attributes [1] and perhaps it's not able to copy name and bar while baz is copied. Below are for FooMock = create_autospec(Foo) . So 'name' in dir(Foo) is False for dataclasses. Is this a known behavior?

dir(Foo)

['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar']


dir(Foo(1, 2))

['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'baz', 'name']

dir(create_autospec(Foo))

['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'bar', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']


print('name' in dir(fooMock)) # False
print('baz' in dir(fooMock)) # False
print('bar' in dir(fooMock)) # True

[0] https://docs.python.org/3/library/unittest.mock.html#unittest.mock.create_autospec
[1] https://github.com/python/cpython/blob/0e10766574f4e287cd6b5e5860a1ca75488f4119/Lib/unittest/mock.py#L2263