Skip to content

Instantly share code, notes, and snippets.

@rpdelaney
Last active November 17, 2019 07:30
Show Gist options
  • Select an option

  • Save rpdelaney/5d72862433e7358e523f5d1d3fa765a2 to your computer and use it in GitHub Desktop.

Select an option

Save rpdelaney/5d72862433e7358e523f5d1d3fa765a2 to your computer and use it in GitHub Desktop.
Python Mocking Mistakes

python-mock and autospec

Sometimes, code I write initially is very much part of the discovery process. Initial attempts will be scrapped and refactored aggressively until I begin to settle on the overall structure and design of the application. At this point I will begin retroactively writing tests for existing code and switching to a more test-driven design approach for new code.

Unfortunately, I've been bitten a few times when using python's mock library by misunderstanding of the pitfalls around autospec defaulting to false, as described in this blog post.

Problem

Mock objects are designed to track all interactions with them, including calls to methods and class attributes that do not exist in the object being mocked. That means making method calls that do not exist in the original object will not raise any kind of error.

Therefore, following a very strict TDD approach is required to ensure that none of the tests are written so that they always pass. In my case, I was doing assert_called_once -- which is not part of the Mock API and so that test will always pass.

Additionally, if code is refactored in such a way that a mocked object's API changes, but the tests are not refactored with it, then they will continue to pass although their new API is not being tested.

Solution

In a mock object, Auto-speccing is disabled by default. If it is enabled, a the instantiated Mock copies the API of the mocked object into its own spec recursively. Calls to non-existent class attributes will result in exceptions.

If we want to use auto-speccing by default without adding the autospec parameter to every last call to mock.patch(), we can define our own patch object:

import mock

def my_patch(*args, autospec=True, **kwargs):
    return mock.patch(*args, autospec=autospec, **kwargs)

Alternatively:

import mock
import functools

my_patch = functools.partial(mock.patch, autospec=True)

The mock library also includes the create_autospec() function.

Unanswered questions

  • mock provides a MagicMock and a NonCallableMagicMock object. When patching an object, does using autospecing or not affect whether the mocked object is callable?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment