So back to spec and why its important:
Basically when you create a MagicMock, you have an object which is not bounded to any interface. This makes testing easy, but on the other hand, your code does use interfaces and they do change over time.
Here is a quick example:
Lets say I'm using a class called HashAPI which provides hashing services and exposes the following interface:
class HashAPI:
def create_hash(self, key):
return hash(key)My own class, MyFileStore, allows you to pass an instance compatible with HashAPI:
class MyFileStore:
def __init__(self, hasher):
self._hasher = hasher
def get(self, path_):
return self._hasher.create_hash(path_)And I wrote the following tests:
class TestMyFileStore:
def test_get(self):
mock_hasher = mock.MagicMock()
mock_hasher.create_hash.return_value = 'some_hash'
store = MyFileStore(
hasher=mock_hasher,
)
assert store.get('some_path') == 'some_hash'
mock_hasher.create_hash.assert_called_with('some_path')Looks good, right?
Now, for the sake of the example, lets say HashAPI is an external library (obviously it doesn't need to be), and after a while, someone removed create_hash and provide a new method instead:
class HashAPI:
def create_safe_hash(self, key):
passNow, even though the method create_hash doesn't exist anymore, my tests in TestMyFileStore - would still pass!!!
The solution to this, is specing - create a mock object which has the same interface as the object its trying to replace. unittest.mock has built-in support for that, replace:
mock_hash = mock.MagicMock()with:
mock_hash = mock.MagicMock(spec=HashAPI)Going back to our example - this would have caused the test to fail when the method was removed.
Moreover, there is also the autospec option, when mocking instances.
There are some drawbacks though:
- In order to
specmock needs to introspect the class, and some libraries might have side-effects, for example - opening connections. Obviously it is a bad design pattern to have side-effects on introspecting, but it happens. - For "complicated" objects, it can make tests sometimes slow. Though that is usually not the case.
- There are limitations for what
mockcanspec, see the docs for more details - Sometimes, you are not sure what is the object you need to
spec. Although this can be a problem - it is worth checking whether something else is wrong.
Use spec whenever you can.
For more on that (with focus on autospec): https://docs.python.org/3/library/unittest.mock.html#auto-speccing