2023-02-17

unittest mock

What is mock in unittest

When writing tests using Python's unittest library, you can use mocks to write dependency-free tests. Mocking allows you to simulate interactions with external resources and other modules, increasing the reliability and speed of your tests.

The following are the basic steps for writing tests using mocks with Python's unittest library.

  1. Import the unittest.mock module
python
from unittest.mock import MagicMock, Mock, patch
  1. Import modules, functions, and classes to be tested
python
from my_module import my_function
  1. Create a test class and define test methods
python
import unittest

class TestMyFunction(unittest.TestCase):
    def test_my_function(self):
        # write test code here
  1. Create mock objects in test methods to replace functions and methods under test
python
import unittest
from unittest.mock import MagicMock

class TestMyFunction(unittest.TestCase):
    def test_my_function(self):
        mock_obj = MagicMock(return_value=42)
        with patch('my_module.my_function', mock_obj):
            result = my_function()
        self.assertEqual(result, 42)

In this example, when my_module.my_function() is called, mock_obj is called and the value specified in return_value is returned. Tests show that the return value of my_function() is 42.

Mocks and patches

Mocks in Python's unittest are objects that are used in place of real objects during testing, allowing you to simulate the behavior you need during testing, even if you rely on other objects in your code. This eliminates the need to access external resources such as real databases or networks during testing. using Mock objects increases the speed of testing and improves test reliability.

Python's unittest patch is a feature that allows you to use Mock to modify the code being executed during testing. Patches are used to replace real objects during testing to simulate the required behavior. A patch may be used during a single test or shared by all tests by setting it up in a setUp method. For example, when testing a method that calls an external API, the test execution time will increase if the API is actually called, and the test may fail if the API is offline. Therefore, a patch can be used to mock the API calls and simulate the data retrieved from the API during testing. This increases the speed of testing and ensures that the test will not fail even if the API is offline.

Term Description
mock An object that has the ability to check arguments and the number of times it is called, and can be configured to return values and send exceptions at will
patch Replace system functions and classes with Mock objects

Key classes and functions in unittest.mock

The unittest.mock package contains the following main classes and functions.

  • Mock: Mock object used to replace other objects
  • MagicMock: Subclass of the Mock class with magic methods
  • patch: Context manager for replacing objects used in tests

Using these classes and functions, you can easily mock external objects during testing. For example, if you have a function used to read a file, you can use a mock file object to test what to do if the file does not exist.

Mock

Python's unittest module has a Mock class for creating mock objects for unit testing. The Mock class is an object that mocks a single attribute or method. You can set arbitrary return values for attributes or methods.

python
from unittest.mock import Mock

# create mock object
mock_obj = Mock()

# set attribute
mock_obj.attr = 'value'

# assert attribute
assert mock_obj.attr == 'value'

# set method
mock_obj.method.return_value = 42

# call method
result = mock_obj.method()

# verify result
assert result == 42

MagicMock

MagicMock is a class in Python's unittest.mock module and a subclass of the Mock class. MagicMock has all the features of the Mock class, plus the ability to automatically generate attributes and methods.

Using MagicMock saves you time when writing test code, since calling an object's attributes will automatically return another MagicMock object. In addition, the values set on the attributes are saved and can be accessed later.

For example, you can create a MagicMock object like this.

python
from unittest.mock import MagicMock

magic_mock_obj = MagicMock()

Any attribute can be set for this object.

python
magic_mock_obj.some_attribute = 10

In this case, the some_attribute attribute is set to the value 10. You can also make assertions on attributes.

python
assert magic_mock_obj.some_attribute == 10

MagicMock automatically creates a MagicMock object on attribute access. For example, if you have the code magic_mock_obj.some_object.some_method(), even if the attribute some_object does not exist, no error will occur and a MagicMock object will be automatically generated. This saves time when writing test code.

The MagicMock object also has the ability to track information about the call, just like the Mock object. For example, you can track the number of times the MagicMock object is invoked, its arguments, etc.

Differences from Mock

In Python unittest, Mock and MagicMock are both tools for creating mock objects, but there are subtle differences.

Mock objects are used to mock callable functions and classes; Mock objects can make assertions on mocked objects and validate arguments passed at call time.

MagicMock, on the other hand, is a subclass of the Mock object and can also mock access to attributes. Specifically, attributes can be set and Mock objects can be automatically created when a method is invoked.

In other words, a Mock object can mock only callable objects, whereas MagicMock can mock not only callable objects but also attributes.

For example, given the following class, the Mock object can only mock the get_name method, while MagicMock can mock not only the get_name method, but also the name attribute.

python
class Person:
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

Therefore, if you need to mock an attribute, using MagicMock is appropriate.

patch

The unittest.mock module has a patch() function used for patching modules, classes, objects, or functions. The patch() function returns a mocked object and can only be used within the context in which the patch was applied.

The patch() function can also be used as a decorator. In this case, the patched object is passed as an argument to the decorated function.

The following is an example of a patch() function. It mocks the get() function in the requests module so that no HTTP request is actually made.

python
from unittest.mock import patch
import requests

@patch('requests.get')
def test_requests(mock_get):
    mock_response = 'Mock Response'
    mock_get.return_value.text = mock_response

    response = requests.get('https://www.example.com')
    assert response.text == mock_response
    assert mock_get.called

In this example, we are patching requests.get. The patch() function is passed the name or path of the object to be patched as an argument. In this example, the path requests.get in string format is used to patch requests.get.

Within the test_requests() function, a mock object mock_get is created and the text attribute is set to mock_response. Next, we set mock_get to return_value. This will cause the mock_response to be returned when the requests.get() function is called.

Finally, requests.get() is called to verify the response text and that the called attribute of the mock object is True.

Mock object attributes and methods

Mock objects have various attributes and methods. The following are the main attributes and methods that a Mock object has.

return_value

The return_value attribute of the Mock object is used to set and retrieve the value returned when the mock is invoked.

For example, suppose you have the following mock.

python
from unittest.mock import Mock

mock = Mock()

Use the return_value attribute to set the value returned when this mock is called.

python
mock.return_value = 10

In this case, 10 is always returned when the mock is called.

python
assert mock() == 10

You can also use the return_value attribute to get the value returned when the mock is called.

python
assert mock.return_value == 10

The return_value attribute is used to control the value returned when the mock is called. When used in a test, it can be used to verify that the value is returned as expected. It can also be used to test a specific behavior of a program by setting the value returned when the mock is called.

side_effect

The side_effect attribute of the Mock object is used to set the function to be executed when the mock is called.

Here is an example of a mock that uses side_effect to throw an exception.

python
from unittest.mock import Mock

def func():
    raise ValueError("Error")

mock = Mock(side_effect=func)

Calling this mock executes the specified function.

python
mock()
# ValueError: Error

The side_effect is useful when what you return as a result of a call is not a simple value, or when you want to return different results for multiple calls. For example, suppose you have a mock with a function that returns a list as follows.

python
mock = Mock()
mock.side_effect = [[1, 2], [3, 4], [5, 6]]

This mock returns [1, 2] on the first call, [3, 4] on the second call, and always [5, 6] on the third and subsequent calls.

python
assert mock() == [1, 2]
assert mock() == [3, 4]
assert mock() == [5, 6]
assert mock() == [5, 6]

The side_effect is used to set the function that will be executed when the mock is called. When used in a test, it can be used to check if a function is executed as expected.

called

The called attribute of a Mock object is a boolean value indicating whether or not the mock has been called. It is True if the mock has been ## called, and False if it has not.

For example, suppose you have the following mock.

python
from unittest.mock import Mock

mock = Mock()

In the state before this mock is called, the value of the called attribute is False.

python
assert mock.called == False

When this mock is called, the value of the called attribute will be True.

python
mock()
assert mock.called == True

The called attribute can be used to verify that the mock has been called. This is useful, for example, if you need to do some processing after verifying that the mock is called.

call_args

The call_args attribute of the Mock object is an object that contains the arguments of the last time the mock was called. This object is returned in the form of a unittest.mock.call object.

For example, suppose we have the following mock.

python
from unittest.mock import Mock

mock = Mock()
mock(1, 2, 3)

In this case, the call_args attribute can be used to get the arguments of the last time the mock was called.

python
assert mock.call_args == ((1, 2, 3),)

Thus, the call_args attribute can be used to retrieve the last argument called and validate it in a test. You can also use the call_args attribute to see how the mock was called.

The call_args attribute can be used in conjunction with convenience assertion methods such as assert_called_with(). It can also be used to check if the mock received arguments as expected.

call_args_list

The call_args_list attribute of a Mock object is a list of tuples representing all arguments when the mock is called. It can be used to record all arguments passed when the mock was called.

For example, suppose we have the following mock.

python
from unittest.mock import Mock

mock = Mock()

If you call this mock twice, passing different arguments each time, the call_args_list attribute will contain a tuple containing each argument.

python
mock('foo')
mock('bar')

assert mock.call_args_list == [('foo',), ('bar',)]

The call_args_list attribute can be used to retrieve all arguments passed when the mock is called and validate them in tests. It also prevents the test from failing if the order or number of arguments is changed.

Note that the call_args_list attribute stores the arguments in the order in which the mock was called, so you will get different results if they are called in a different order.

call_count

The call_count attribute of a Mock object is an integer value that returns the number of times the mock has been called. In other words, it is used to check how many mocks have been called.

For example, suppose you have the following mock

python
from unittest.mock import Mock

mock = Mock()

If this mock is called three times, the call_count attribute will contain the number of calls, 3.

python
mock()
mock()
mock()

assert mock.call_count == 3

The call_count attribute can be used to get the number of times a mock has been called and verified in a test. For example, it can be used to check if a mock has been called the expected number of times.

The call_count attribute can be used in conjunction with convenience assertion methods such as assert_called_once(). The call count can also be monitored to ensure that the program is working properly or that an error has occurred.

mock_calls

The mock_calls attribute of a Mock object is a list of method calls representing all the calls made when that mock was invoked. In other words, it is used to record the method name, arguments, return value, and other information when the mock was invoked.

For example, suppose we have the following mock.

python
from unittest.mock import Mock

mock = Mock()

If this mock is called twice, each time passing different arguments, the mock_calls attribute will contain an object with information about each call.

python
mock('foo')
mock('bar')

assert mock.mock_calls == [call('foo'), call('bar')]

The mock_calls attribute can be used to obtain information about when the mock was called and can be verified in tests. For example, it can be used to verify that the mock was called in the expected order.

The mock_calls attribute is very useful for debugging and validating tests because it contains information such as the name of the method called, its arguments, and the return value. However, since this attribute may contain a large amount of information, it may be desirable to limit it to a reasonable number of calls.

reset_mock

The reset_mock method is used to reset the past state of a Mock object, including whether it has been called before and the arguments and return value of the call. This is especially useful when the same Mock object is used across multiple tests.

Calling reset_mock will reset the following attributes.

  • call_args
  • call_count
  • call_args_list
  • mock_calls
  • return_value
  • side_effect

For example, consider the following Mock object.

python
from unittest.mock import Mock

mock = Mock(return_value=42)
mock(1, 2, 3)

This Mock object is called once with arguments 1, 2, and 3 and returns 42 as the return value. Calling reset_mock will reset these attributes.

python
mock.reset_mock()
assert mock.call_count == 0
assert mock.call_args is None
assert mock.call_args_list == []
assert mock.mock_calls == []
assert mock.return_value is None
assert mock.side_effect is None

Thus, reset_mock can be used to reuse Mock objects between tests.

assert_called

The assert_called() method is used to assert that a Mock object has been called at least once. This method only works if the Mock object is called like a normal function or method.

For example, consider the following Mock object.

python
from unittest.mock import Mock

mock_obj = Mock()

To assert that this Mock object has been called one or more times, do the following.

python
# assert that the mock object has been called at least once
mock_obj.assert_called()

This method raises an exception if the Mock object is not called. Therefore, the Mock object must be called at least once before asserting the call. Alternatively, the assert_called_once() method can be used if the call must be made once.

assert_has_calls

The has_assert_calls() method is used to assert whether the Mock object has received the expected calls. This method can be one or more call objects representing the expected calls.

This method can be used as follows.

python
from unittest.mock import Mock, call

mock_obj = Mock()
mock_obj(1, 2, 3)
mock_obj('a', 'b', 'c')

# asserts that the mock object has been called in the specified order
mock_obj.assert_has_calls([call(1, 2, 3), call('a', 'b', 'c')])

mock_obj.has_assert_calls([call(1, 2, 3), call('a', 'b', 'c')])

The above example asserts that the Mock object mock_obj has received two calls, call(1, 2, 3) and call('a', 'b', 'c'). The assert_has_calls() method also checks that the calls are in the specified order.

assert_called_once

assert_called_once is a method that asserts that a Mock object has been called only once. Before calling assert_called_once, you can also call assert_called or assert_called_with to verify that the Mock object was called only once.

python
from unittest.mock import MagicMock

mock_obj = MagicMock()

mock_obj()

mock_obj.assert_called_once()

assert_called_with

assert_called_with is a method that verifies that a Mock object has been called with the specified arguments. The test will pass only if the called argument matches the expected value.

The following is an example of using assert_called_with.

python
from unittest.mock import MagicMock

def test_example():
    mock_obj = MagicMock()
    mock_obj('foo', 'bar', baz='bazval')
    mock_obj.assert_called_with('foo', 'bar', baz='bazval')

This test creates a mock object and calls it with the arguments 'foo' and 'bar' and the keyword argument baz='bazval'. The assert_called_with method is then used to verify that mock was called with the correct arguments.

Thus, the assert_called_with method can be used to easily verify that the mock was called with the correct arguments. The assert_called_with method can also be used to simultaneously verify information such as the number of calls.

Mock examples

Here are some examples of using Mock.

Mock a function in a module

The following is an example of mocking the get function of the requests module.

python
import requests
from unittest.mock import Mock

def test_fetch_data():
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"foo": "bar"}

    requests.get = Mock(return_value=mock_response)

    result = fetch_data()
    assert result == {"foo": "bar"}

In this example, we mock the requests.get function and set the return value to a Mock object, which has a status_code attribute and a json() method. The json() method is set to return a Mock object by the return_value attribute.

Mock methods of a class

The following is an example of mocking the add method of the Calculator class.

python
from unittest.mock import Mock

class Calculator:
    def add(self, x, y):
        return x + y

def test_calculator():
    # mock the get function in the requests module
    calculator = Calculator()

    # mock the add method of the Calculator class
    calculator.add = Mock(return_value=10)

    # run test
    result = calculator.add(1, 2)
    assert result == 10

This example mocks the add method of the Calculator class and sets the return value to 10.

Test for exceptions using mock objects

The following is an example of testing for exceptions using a mock object.

python
from unittest.mock import Mock

def test_divide_by_zero():
    mock_logger = Mock()

    import logger
    logger.error = mock_logger

    try:
        1 / 0
    except ZeroDivisionError as e:
        mock_logger.assert_called_with("division by zero")

In this example, we mock the error function of the logger module to test that a ZeroDivisionError exception raised by a zero division causes the logger.error function to be called.

MagicMock example

The following is a simple example using MagicMock. As an example, we have code that calls the get() function of the requests module. This example mocks the response that is returned when the get() function is called.

python
import requests
from unittest.mock import MagicMock

def test_requests():
    response_mock = MagicMock()
    response_mock.status_code = 200
    response_mock.json.return_value = {'key': 'value'}

    requests.get = MagicMock(return_value=response_mock)

    response = requests.get('https://www.example.com')
    assert response.status_code == 200
    assert response.json()['key'] == 'value'

This example creates a response_mock and sets its status_code attribute. It also mocks the json() method and sets its return value. Next, we replace requests.get with MagicMock and set return_value to response_mock. Finally, requests.get('https://www.example.com') is called to verify the response status code and JSON value.

patch examples

Here are some examples of using patch.

Mock a function in an external module

For example, suppose you have a function that uses the following math module.

python
import math

def calculate_square_root(num):
    return math.sqrt(num)

Within this calculate_square_root function, you can mock the math.sqrt function. Here is an example of mocking math.sqrt using patch.

python
import unittest
from unittest.mock import patch
from my_module import calculate_square_root

class TestCalculateSquareRoot(unittest.TestCase):
    @patch('my_module.math.sqrt')
    def test_calculate_square_root(self, mock_sqrt):
        mock_sqrt.return_value = 2.0
        result = calculate_square_root(4)
        self.assertEqual(result, 2.0)

The above example uses the @patch('my_module.math.sqrt') decorator to mock math.sqrt. We also set mock_sqrt.return_value to set the return value of the mock.

Mock an object's attributes

Suppose you have the following class.

python
class MyClass:
    def __init__(self):
        self.my_attribute = 42

    def my_method(self):
        return self.my_attribute

You can mock the my_attribute attribute of an instance of this class. The following is an example of mocking my_attribute attribute using patch.

python
import unittest
from unittest.mock import patch
from my_module import MyClass

class TestMyClass(unittest.TestCase):
    @patch('my_module.MyClass.my_attribute', new=50)
    def test_my_method(self):
        my_instance = MyClass()
        result = my_instance.my_method()
        self.assertEqual(result, 50)

The above example uses the @patch('my_module.MyClass.my_attribute', new=50) decorator to mock the my_attribute attribute.

Mock file opening

The following is an example of a function that reads a file.

python
def read_file(filename):
    with open(filename, 'r') as f:
        return f.read()

To test this function, a mock file object can be used to test if the file does not exist. To do this, use the patch function to replace the file and set it to return a mock file object.

python
from unittest.mock import patch

def test_read_file():
    with patch('builtins.open', return_value=Mock(spec=open)) as mock_file:
        mock_file.return_value.__enter__.return_value.read.return_value = 'test file contents'
        assert read_file('test.txt') == 'test file contents'

In this example, builtins.open is replaced with a mock object. The mock_file.return_value represents the object returned by the open function. The mock file object returns a file object in the __enter__ method and the contents of the file in the read method. You can replace methods of a mock file object in the patch context.

References

https://docs.python.org/3/library/unittest.mock.html

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!