Recipes

Classes as factories

We want to test the following code:

import requests

def fetch(url):
    session = requests.Session()
    return session.get(url)

In a traditional sense this code is not designed for testability. But we don’t care here.

Python has no new keyword to get fresh instances from classes. Man, that was a good decision, Guido! So the uppercase S in requests.Session() doesn’t have to stop us in any way. It looks like a function call, and we treat it like such: The plan is to replace Session with a factory function that returns a (mocked) session:

from mockito import when, mock, verifyStubbedInvocationsAreUsed

def test_fetch(unstub):
    url = 'http://example.com/'
    response = mock({'text': 'Ok'}, spec=requests.Response)
    # remember: `mock` here just creates an empty object specced after
    #           requests.Session
    session = mock(requests.Session)
    # `when` here configures the mock
    when(session).get(url).thenReturn(response)
    # `when` *patches* the globally available *requests* module
    when(requests).Session().thenReturn(session)  # <=

    res = fetch(url)
    assert res.text == 'Ok'

    # no need to verify anything here, if we get the expected response
    # back, `url` must have been passed through the system, otherwise
    # mockito would have thrown.
    # We *could* ensure that our mocks are actually used, if we want:
    verifyStubbedInvocationsAreUsed()

Faking magic methods

We want to test the following code:

import requests

def fetch_2(url):
    with requests.Session() as session:
        return session.get(url)

It’s basically the same problem, but we need to add support for the context manager, the with interface:

from mockito import when, mock, args

def test_fetch_with(unstub):
    url = 'http://example.com/'
    response = mock({'text': 'Ok'}, spec=requests.Response)

    session = mock(requests.Session)
    when(session).get(url).thenReturn(response)
    when(session).__enter__().thenReturn(session)  # <=
    when(session).__exit__(*args)                  # <=

    when(requests).Session().thenReturn(session)

    res = fetch_2(url)
    assert res.text == 'Ok'

Properties and descriptors

We want to test the following code:

class Settings:
    @property
    def timeout(self):
        return 30

def build_timeout_header(settings):
    return {'X-Timeout': str(settings.timeout)}

For property stubs, patch the class, not the instance:

from mockito import when

def test_timeout_header(unstub):
    settings = Settings()

    with when(Settings).timeout.thenReturn(5):
        assert build_timeout_header(settings) == {'X-Timeout': '5'}

    assert build_timeout_header(settings) == {'X-Timeout': '30'}

You can also combine one-off values with the original property implementation:

def test_timeout_header_one_off_then_original(unstub):
    settings = Settings()

    with when(Settings).timeout.thenReturn(5).thenCallOriginalImplementation():
        assert build_timeout_header(settings) == {'X-Timeout': '5'}
        assert build_timeout_header(settings) == {'X-Timeout': '30'}

Trying to stub a property on an instance is intentionally rejected with a clear error; use class-level stubbing instead (when(Settings).timeout…).

Deepcopies

Python’s deepcopy is tied to __deepcopy__, in a nutshell deepcopy(m) will call m.__deepcopy__(). For a strict mock, deepcopy(m) will raise an error as long as the call is unexpected – as usual.

While you could completely fake it –

when(m).__deepcopy__(...).thenReturn(42)

– you could also enable the standard implementation by configuring the mock, e.g.

mock({"__deepcopy__": None}, strict=True)

Dumb mocks are copied correctly by default.

However, there is a possible catch: deep mutable objects must be set on the mock’s instance, not the class. And the constructors configuration is set on the class, not the instance. Huh? Let’s show an example:

m = mock()
m.foo = [1]  # <= this is set on the instance, not the class

m = mock({"foo": [1]})  # <= this is set on the class, not the instance

Don’t rely on that latter “feature”, initially the configuration was meant to only set methods, and especially special, dunder methods, – and properties. Property support is available via when(MyClass).prop… too, but constructor dict values are still set on the class for compatibility.

Btw, copy will just work for strict mocks and does not raise an error when not configured/expected. This is just not implemented and considered not-worth-the-effort.

Shared setUp stubs with tearDown safety checks

Sometimes you have one “big” fixture / setUp that configures reusable stubs.

Only some tests actually need all of them, but you also want to call verifyStubbedInvocationsAreUsed() or ensureNoUnverifiedInteractions() unconditionally as your safety net on tearDown.

Yeah, I hate that but we need to be realistic. Use between=(0,) like so:

class TestService:
    def setUp(self):
        self.client = mock()
        when(self.client).fetch("/warmup").thenReturn({"ok": True})
        ...  # more

    def tearDown(self):
        verify(self.client, between=(0,)).fetch(...)  # mark as ok!
        verifyStubbedInvocationsAreUsed()
        ensureNoUnverifiedInteractions()

Speccing from typing.Protocol

If your production code uses typing.Protocol interfaces, you can use them as mock(spec=...) input directly:

from typing import Protocol
from mockito import mock, when

class Service(Protocol):
    async def fetch(self, path: str) -> str:
        ...

    def close(self) -> bool:
        ...

service = mock(Service)
when(service).fetch('/health').thenReturn('ok')
when(service).close().thenReturn(True)

assert await service.fetch('/health') == 'ok'  # async stays async
assert service.close() is True                  # sync stays sync

Such mocks are strict by default, so unknown methods and invalid call signatures still fail early.