« Back to Index

[Python Mocking]

View original Gist on GitHub

Tags: #python #mocking #mocks #tornado

Python Mocking.md

unittest.mock or mock

The mock module is a backwards compatible library you can download from PyPy, where as unittest.mock is the same thing but only compatible with the version of Python you’re using.

So in almost all cases you’ll want to import it like so:

import unittest.mock as mock

For more examples, see this reference guide

Verify Exceptions

In the following example our code (in app.account.confirm(...)) catches a generic Exception and re-raises it as exceptions.CognitoException, which we catch and make assertions against:

@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))
@mock.patch('app.account.instr_exc')
def test_account_confirm_failure(mock_instr_exc, mock_signup):
    with pytest.raises(exceptions.CognitoException) as exc_info:
        app.account.confirm(123, 'foo')
        assert True is True  # this will never be executed!
        
    assert exc_info.typename == 'CognitoException'
    assert str(exc_info.value) == 'SIGNUP_CONFIRMATION_FAILED'

    # we can't have a single assert against call_args_list because in python
    # two instances of an exception aren't considered equal, and so we have to
    # assert individual elements of call_args_list.
    assert type(mock_instr_exc.call_args_list[0][0][0]) == Exception
    assert str(mock_instr_exc.call_args_list[0][0][0]) == 'whoops'
    assert mock_instr_exc.call_args_list[0][0][1] == 'SIGNUP_CONFIRMATION_FAILED'
    assert mock_instr_exc.call_args_list[0][1] == {'code': '123'}

Note: don’t make the mistake of putting any assertions within the with context manager. Once the Exception is raised by the function being called within the with context manager, all code after it inside the block is not executed.

Clearing lru_cache

If a function you wish to test has the functools.lru_cache decorator applied, then you’ll need to be mindful of mocking the response of that function as it’ll be cached in one test and the cached result will be returned when calling the function again to test some other behaviour (and might likely confuse you when you see the unexpected response).

To fix this issue is very easy because lru_cache provides additional functions when decoratoring your functions, it provides:

The latter (cache_clear) is what you call…

@lru_cache(5)
def foo():
    print('Executing foo...')
    
foo()  # Executing foo...
foo()  # <nothing printed as None response was cached and returned>
foo.cache_info()  # CacheInfo(hits=1, misses=1, maxsize=5, currsize=1)
foo.cache_clear()
foo()  # Executing foo... (notice the 'side effect of print is executed again)

Note: debugging this isn’t always obvious. Later on I demonstrate how to mock the builtin open function, and in that scenario I stumbled across this issue, because although I wasn’t mocking the top level function itself (I was mocking the call to open within), the contents of the file being opened was what was returned and being cached.

Mock Module Level/Global Variables

With a module variable you can can either set the value directly or use mock.patch.

In the following example we have the variable client_id which is a global variable inside the app.aws module which we import to reference elsewhere in our code:

import app.aws


def test_account_confirm_successful():
    app.aws.client_id = 456  # used internally by `confirm()`
    ...
    
@mock.patch('app.aws.client_id', 456)
def test_account_confirm_successful():
    ...

In the mock.patch example, there are two key things to notice:

  1. we don’t use return_value.
  2. there is no mock instance passed to the test function.

Mock Instance Method

Mock the entire class and take advantage of the fact that a mock, when called, returns a new mock:

@mock.patch("foo.bar.SomeClass")
def test_stuff(mock_class):
    mock_class.return_value.made_up_function.return_value = "123"

The reason this ^^ works is because calling a mock returns another mock, and so if you call mock_class.return_value you’re actually getting another mock object; and because you can call anything you like on a mock object (a function or property you call on a mock doesn’t have to exist), means you can set a return_value on the mock that’s returned by calling made_up_function.

Mock Class Method

Similar approach to mocking an instance method in that you mock the entire class but you have one less return_value to assign to:

mock_class.ClassMethodName.return_value = "123"

Mock Entire Class

To mock an entire class you’ll need to set the return_value to be a new instance of the class.

@mock.patch('myapp.app.Car')
def test_class(self, mock_car):

    class NewCar(object):
        def get_make(self):
            return 'Audi'

        @property
        def wheels(self):
            return 6

    mock_car.return_value = NewCar()
    ...

See other class related mocking tips here

Mock Async Calls

We can create a coroutine and allow it to be configurable for different types of responses:

def make_coroutine(response):
    """You could pass response as a mock or as a raw data structure."""
    
    async def coroutine(*args, **kwargs):
        """Imagine this coroutine is called with a url as the first argument."""
        
        if args[0] == '/exception/foo':
            raise Exception('Whoops Foo')
        elif args[0] == '/exception/bar':
            raise Exception('Whoops Bar')
        return response
    return coroutine
    
class TestTornado(AsyncHTTPSTestCase):
    def get_app(self):
        class FakeHandler(tornado.web.RequestHandler):
            async def get(self, *args, **kwargs):
                self.finish('hello')
                
        return tornado.web.Application([
            (r'/fake', FakeHandler),
        ])
    
    @mock.patch('foo.bar.http_client')
    def test_async thing(self, mock_client):
        response_body = {'state': 'success', 'payload': 'foobar'}
        response_mock = mock.MagicMock()
        response_mock.body = json.dumps(response_body)
        
        mock_client.post.side_effect = make_coroutine(response_mock)

        response = self.fetch('/fake')
        assert response.code == 200
        assert json.loads(response.body.decode()) == response_body

Note: you could also create a MagicMock and set properties on it like m = mock.MagicMock(x=1, y=2, z=3) and then pass that into the make_coroutine function. That way, within each if statement you can then just call m.x or m.y etc to get at the actual response you want to return (rather than having to hardcode response objects within the function itself). Mock is also considered callable (see below implementation).

When dealing with side_effects that need to sometimes trigger an Exception and other times suceed you could use a slightly modified mock implementation that checks if the given response object is callable or not…

count = 0

def make_side_effect_coroutine(side_effect):
    """Side effect friendly mock coroutine.

    In some tests we need to have a mocked coroutine return a different value
    when it's called multiple times, but a mock side_effect can't trigger a
    raised exception when given an iterator, and so we have to construct that
    behaviour ourselves.
    """

    async def coroutine(*args, **kwargs):
        return side_effect(*args, **kwargs) if callable(side_effect) else side_effect
    return coroutine
    
@mock.patch('app.thing')
def test_confirm_email_change_failure(self, mock_thing):

    def side_effects(*args, **kwargs):
        """Use global var to control mock side effects."""

        global count

        if count > 0:
            raise Exception('whoops')

        count += 1
        return  # don't raise an exception the first time around

    mock_thing.side_effect = make_side_effect_coroutine(side_effects)

Alternatives…

Monkey Patch

# allow mock to be used as an await expression...

async def async_response():
    return namedtuple('_', ['body'])('{"state": "success"}')


def mock_async_expression(our_mock):
    return async_response().__await__()


mock.MagicMock.__await__ = mock_async_expression

MagicMock Subclass

class AsyncMock(MagicMock):
    async def __call__(self, *args, **kwargs):
        return super(AsyncMock, self).__call__(*args, **kwargs)
        
class TestHandlers(testing.AsyncTestCase):
    @mock.patch('app.handlers.trigger_soft_cdn_purge', new_callable=AsyncMock)
    @mock.patch('app.handlers.api')
    @testing.gen_test
    async def test_update_cache(self, api_mock, trigger_soft_cdn_purge):
        response = mock.MagicMock()
        response.code = 200
        api_mock.buzz = AsyncMock(return_value=response)

Async Inline Function

@mock.patch('app.buzz_api.api_gateway')
@testing.gen_test
async def test_buzz_api(self, client_mock):
    async def get(url, **kwargs):
        return
        
    client_mock.get.side_effect = get

Mock Instance Types

There are two ways to make a mock more like the real object being mocked.

  1. spec
  2. wrap

We can use mock’s spec feature to mimic all methods/attributes of the object being mocked. This ensures your mocks have the same api as the objects they are replacing.

Note: there is a stricter spec_set that will raise an AttributeError.

This is best demonstrated with an example:

import unittest.mock as mock
import tornado.simple_httpclient

from tornado.httpclient import AsyncHTTPClient


http_client = AsyncHTTPClient()
type(http_client)  # tornado.simple_httpclient.SimpleAsyncHTTPClient

isinstance(http_client, tornado.simple_httpclient.SimpleAsyncHTTPClient)  # True

isinstance(mock.MagicMock(), tornado.simple_httpclient.SimpleAsyncHTTPClient)  # False

m = mock.MagicMock(spec=tornado.simple_httpclient.SimpleAsyncHTTPClient)
isinstance(m, tornado.simple_httpclient.SimpleAsyncHTTPClient)  # True

The wrap parameter allows you to ‘spy’ on the implementation, as wll as affect it’s behaviour:

@pytest.mark.parametrize("input_date, input_url, valid", [
    ("2017-06-17T00:00:00.000000Z", "foo", True),
    ("2017-06-18T00:00:00.000000Z", "bar", False),
])
@mock.patch("app.handlers.data.datetime", wraps=datetime)
def test_valid_video(mock_datetime, input_date, input_url, valid):
    mock_datetime.now.return_value = datetime(2017, 6, 18, 00, 00, 00, 000000)
    assert valid_video(input_date, input_url) is valid

Mock return_value vs side_effect

If your function has a try/except around it, then you can use side_effect to cause the calling of the function to trigger an Exception as the returned value:

@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))

Note: if you had used return_value=Exception('whoops') then the mock would return the string representation of the Exception rather than raising an exception like side_effect does.

Otherwise if you just need a static value returned, so it’s evaluated at the time it’s defined (not when it’s called), then you can use return_value instead:

@mock.patch('app.security.secret_hash', return_value='###')

Mock Nested Calls

Calling a property on a mock returns another mock, so in order to mock very specific properties you’ll need to nest your return_value or side_effect:

m = mock.MagicMock()
m.return_value.get.side_effect = [1, 2]
m.return_value.post.return_value = 'foo'

x = m()

x.get()   # 1
x.post()  # foo
x.get()   # 2

Mock builtin open function

Python’s mock library provides an abstraction for mocking the builtin open function a lot simpler…

def test_load_ui_messages_successful():
    """Verify ui message YAML file can be read properly."""

    file_content = 'foo: bar'

    with mock.patch('bf_auth.utility.open', mock.mock_open(read_data=file_content), create=True) as mock_builtin_open:
        assert utils.load_ui_messages('./path/to/non/existing/file.yaml') == {'foo': 'bar'}

The create=True param set on mock.patch means that the mock.MagicMock returned will automagically create any attributes that are called on the mock (this is because the open function will attempt to access lots of different things and it’s easier for mock to mock out all of that for you).