StrictMock¶
When unit testing, mocks are often used in place of a real dependency, so tests can run independently. Mocks must behave exactly like the real thing, by returning configured canned responses, but rejecting anything else. If this is not true, it is hard to trust your test results.
Let’s see a practical example of that:
In [1]: from unittest.mock import Mock
In [2]: class Calculator:
...: def is_odd(self, x):
...: return bool(x % 2)
...:
In [3]: mock = Mock(Calculator)
In [4]: mock.is_odd(2)
Out[4]: <Mock name='mock.is_odd()' id='140674180253512'>
In [5]: bool(mock.is_odd(2))
Out[5]: True
In [6]: mock.is_odd(2, 'invalid')
Out[6]: <Mock name='mock.is_odd()' id='140674180253512'>
The mock, right after being created, already has dangerous behavior. When is_odd()
is called, another mock is returned. And it is unconditionally True
. And this is wrong: 2 is not odd. When this happens in your test, it is hard to trust its results: it might go green, even with buggy code. Also note how the mock accepts calls with any arguments, even if they don’t match the original method signature.
A Well Behaved Mock¶
StrictMock is safe by default: it only has configure behavior:
In [1]: from testslide import StrictMock
In [2]: class Calculator:
...: def is_odd(self, x):
...: return bool(x % 2)
...:
In [3]: mock = StrictMock(Calculator)
In [4]: mock.is_odd(2)
(...)
UndefinedBehavior: <StrictMock 0x7F290A3DD860 template=__main__.Calculator>:
Attribute 'is_odd' has no behavior defined.
You can define behavior by assigning a value to it.
Instead of guessing what is_odd
should return, StrictMock clearly tells you it was not told what to do with it. In this case, the mock is clearly missing the behavior, that we can trivially add:
In [5]: mock.is_odd = lambda number: False
In [6]: mock.is_odd(2)
Out[6]: False
API Validations¶
StrictMock does a lot of validation under the hood, so you can trust its behavior, even when breaking changes are introduced.
Attribute Existence¶
You won’t be allowed to set an attribute to a StrictMock if the given template class does not have it:
In [1]: from testslide import StrictMock
In [2]: class Calculator:
...: def is_odd(self, x):
...: return bool(x % 2)
...:
In [3]: mock = StrictMock(Calculator)
In [4]: mock.invalid = 'whatever'
(...)
NoSuchAttribute: <StrictMock 0x7F7821920780 template=__main__.Calculator>:
No such attribute 'invalid'.
Can not set attribute invalid that is neither part of template class Calculator or runtime_attrs=[].
Dynamic Attributes¶
StrictMock will introspect at the template class code, to detect attributes that are dynamically defined:
In [1]: from testslide import StrictMock
...:
In [2]: class DynamicAttr(object):
...: def __init__(self):
...: self.dynamic = 'set from __init__'
...:
In [3]: mock = StrictMock(DynamicAttr)
In [4]: mock.dynamic = 'something else'
Note
This feature is not available in Python 2!
The detection mechanism can only detect attributes defined from __init__
. If you have attributes defined at other places, you will need to inform them explicitly:
StrictMock(TemplateClass, runtime_attrs=['attr_name'])
Method Signatures¶
StrictMock also ensures that method signatures match the ones from the template class:
In [1]: from testslide import StrictMock
In [2]: class Calculator:
...: def is_odd(self, x):
...: return bool(x % 2)
...:
In [3]: mock = StrictMock(Calculator)
In [4]: mock.is_odd = lambda number, invalid: False
In [5]: mock.is_odd(2, 'invalid')
(...)
TypeError: too many positional arguments
Note
This feature is not available in Python 2!
Magic Methods¶
Defining behavior for magic methods works out of the box:
In [1]: from testslide import StrictMock
In [2]: mock = StrictMock()
In [3]: mock.__str__ = lambda: 'mocked str'
In [4]: str(mock)
Out[4]: 'mocked str'
Naming¶
You can optionally name your mock, to make it easier to identify:
In [1]: from testslide import StrictMock
In [2]: str(StrictMock())
Out[2]: '<StrictMock 0x7F7A30FC0748>'
In [3]: str(StrictMock(name='whatever'))
Out[3]: "<StrictMock 0x7F7A30FDFF60 name='whatever'>"
Generic Mocks¶
It is recommended to use StrictMock giving it a template class, so you can leverage its API validation. There are situations however, that any “generic mock” is good enough. You can still use StrictMock, although you’ll loose most validations:
In [1]: from testslide import StrictMock
In [2]: mock = StrictMock()
In [3]: mock.whatever
(...)
UndefinedBehavior: <StrictMock 0x7FED1C724C18>:
Attribute 'whatever' has no behavior defined.
You can define behavior by assigning a value to it.
In [4]: mock.whatever = 'something'
In [5]: mock.whatever
Out[5]: 'something'
It will accept setting any attributes, with any values.
Extra Functionality¶
copy.copy()
andcopy.deepcopy()
works, and give back another StrictMock, with the same behavior.- Template classes that use
__slots__
are supported. - If the template class is a context manager, the StrictMock instance will also define
__enter__
, yielding itself, and an empty__exit__
.