How to Use Mock and Patch

🗒️ Introduction

This notes writes down how to create mock in python tests. There are two famous python mocking libraries:

🧠 Tutorial

What's the difference between mock and patch in python mocking ?

patch is used to temporarily replace an object with a mock object in order to isolate and control the behavior of the code being tested. It allows you to replace a specific attribute or object with a mock object during the execution of a test.
mock is a library that provides the ability to create and configure mock objects for testing it.

Creating Mocks

Type Section
attribute #Mock an Attribute
function #Mock an Function
exception #Mock an Exception
context manager #Mock a Context Manager

Mock an Attribute

def test_mock_attribute(mocker):
	mock = mocker.Mock()
	mock.x = 3
	assert mock.x == 3

Mock an Function

We can mocking a function by specify return_value by Mock class.

def test_mock(mocker):
	mock = mocker.Mock()
	mock.foo.return_value = 10
	assert mock.foo() == 10
What is the difference between Mock() and MagicMock() ?

MagicMock is the subclass of Mock. It contains all magic methods pre-created and read to use (e.g. __str__ or __len__). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.

Mock an Exception

You can pass an exception by side_effect

def test_exception_by_side_effect(mocker):
    mock = mocker.Mock()
    mock.foo.side_effect = [10, 11, ValueError('something happend')]
    assert mock.foo() == 10
    assert mock.foo() == 11
    with pytest.raises(ValueError) as exec_info:
        mock.foo()
        assert exec_info.message == 'something happend'
We can use pytest.raises context manager to assert exception

Mock a Context Manager

We can use __enter__ and __exit__ attributes of a mock object to mock a context manager.

def test_context_manager(mocker):
	mock = mocker.MagicMock()
	mock.__enter__.return_value = 'mocked value'
	with mock as value:
		assert value == 'mocked value'

Side_effect v.s. return_value

What is the difference between side_effect and return_value ?

side_effect: Is used when you want a mocked function to have a specific side effect when it is called, rather than returning a specific value. It could be a function, an iterable of values.
return_value: Is used to specify the value that a mocked function ( not a mocked property! ) should return when it is called.

mocking a static return value of a function

def test_return_value(mocker):
	mock = mocker.Mock()
	mock.foo.return_value = '123'
	assert mock.foo() == '123'
	assert mock.foo() == '123'

mocking a dynamic return value of a function

def test_side_effect(mocker):
	mock = mocker.Mock()
	mock.foo.side_effect = [10, 11, 12]
	assert mock.foo() == 10
	assert mock.foo() == 11
	assert mock.foo() == 12

Or you can pass a callable by side_effect


def callable(*args, **kwargs):
	name = kwargs['name']
	return f'{name}-001'

def test_dynamic_side_effect(mocker):
	mock = mocker.Mock()
	mock.foo.side_effect = callable
	assert mock.foo(name='foo') == 'foo-001'

Assert Calls

from unittest.mock import call, Mock
mock = Mock()
mock_obj(1, 2) 
mock_obj('foo', 'bar')

calls = [call(1,2), call('foo', 'bar')]

mock.assert_has_calls(calls)

Spec v.s. spec_set

The spec parameter is used to specify the class or object that the mock should be based on. It is used to restrict the mock object to only have the attributes and methods that are present in the specified class or object. Attempting to access an attribute not in the originating object will raise an AttributeError.

def test_mock_spec(mocker):
	mock = mocker.Mock(spec=3)
	assert instance(mock, int)
class Foo:
	def bar():
		return 'bar'
		
def test_mock_spec_class(mocker)
	mock = mocker.Mock(spec=Foo)
	mock.foo() # raise an AttributeError !
	mock.bar.return_value = 3
	assert mock_bar() == 3
  mock.x = 10
  assertt mock.x == 10

While spec also let you manually set the non-exist attributes or methods but spec_set forbids that and raise an AttributeError.

class Foo:
	def bar():
		return 'bar'

def test_mock_spec_set_class(mocker):
	mock = mocker.Mock(spec_set=Foo)
	mock.x = 10 # raise an AtttributeError !
spec_set should be your first choice.

Creating Patches

Patch a Class Method

class Foo:
	def bar():
		return 'bar'

def test_foo_bar(mocker):
	mocker.patch.object(Foo, 'bar', return_value='mocked!')
	foo = Foo()
	assert foo.bar() == 'mocked'

Patch a Class Async Method

from unittest.mock import AsyncMock
class Foo
	async def bar():
		return 'bar'

@pytest.mark.async
async def test_async_foo_bar(mocker):
	mock = mocker.patch.object(Foo, "bar", new_callable=AsyncMock)
	mock.return_value = "mocked"
	foo = Foo()
	assert await foo.bar() == "mocked"