A note about mock (Python mock library)

I haven't used the mock library properly before, so I did some research.

mock installation

There are several Python mock libraries, but this time I will use mock. In Python 3.3 or later, it is Standard Library, and when writing a program that supports only 3.3 or later Does not need to install external libraries. Actually, it seems that there are many cases where it is not supported only after 3.3, so will it be common to install with pip?

mock installation


$ pip install mock

Replace with mock

The point is the mock.Mock class. The instance of Mock class is callable, and you can set the return value when you call.

Return value setting


>>> from mock import Mock
>>> m = Mock()
# return_Set the return value in the value attribute
>>> m.return_value = 5
>>> m()
5

Replace the Mock instance with the return value set in this way with the class method that is actually processing. Most of what you do is this.

An example is shown using the following code. In the code below, B # b_test depends on the processing of A # a_test. What if I want to test B # b_test alone?

B#b_test and A#a_test dependency


class A(object):
    def a_test(self):
        print('test')

class B(object):
    def __init__(self, a_ins):
        self.a_ins = a_ins
    def b_test(self):
        return self.a_ins.a_test()

In this case, you can separate the dependency by replacing A # a_test with a Mock instance.

Replacement to mock


>>> a = A()
>>> a.a_test = Mock()
>>> a.a_test.return_value = 'mocked'
>>> b = B(a)
>>> b.b_test()
'mocked'

In the above example, you have replaced the methods, but in many cases you want to replace them entirely on an instance basis, not on a method basis. In that case, when creating a Mock instance, specify the class you want to mock in the spec argument.

Replacement to mock(use spec)


>>> a = Mock(spec=A)
>>> a.a_test.return_value = 'mocked_spec'
>>> b = B(a)
>>> b.b_test()
'mocked_spec'

Mock calls are recorded in the Mock instance. You can use this information to check the relationships between instances (= whether the call is correct).

Record of mock calls


>>> a = Mock(spec=A)
>>> a.a_test.return_value = 'mocked_spec'
>>> b = B(a)
# Mock#call_args_list:List to store invocations for the appropriate Mock instance
>>> a.a_test.call_args_list
[]

# Mock#assert_any_call:Assert if the corresponding Mock instance call was in the past.
#In this example, there is no argument, but in reality it is possible to give any argument(Assert if there was a Mock instance call with that argument)
# (ref. http://www.voidspace.org.uk/python/mock/mock.html)
>>> a.a_test.assert_any_call()
Traceback (most recent call last):
  File "<ipython-input-81-ed526bd5ddf7>", line 1, in <module>
    a.a_test.assert_any_call()
  File "/Users/tatsuro/.venv/py3.3/lib/python3.3/site-packages/mock.py", line 891, in assert_any_call
    '%s call not found' % expected_string
AssertionError: a_test() call not found

>>> b.b_test()
'mocked_spec'

# B#b_test()Called via
>>> a.a_test.call_args_list
[call()]
>>> a.a_test.assert_any_call()
>>>

Mock the process of returning an exception

If you want to return a return value, you can set it to return_value, but what if you want to mock the process of throwing an exception as shown below?

Processing to throw an exception


class A(object):
    def a_test(self):
        raise ValueError

class B(object):
    def __init__(self, a_ins):
        self.a_ins = a_ins
    def b_test(self):
        try:
            return self.a_ins.a_test()
        except ValueError:
            print('error handling')

In that case, use Mock # side_effect. By setting an exception here, you can throw an exception when you call the mock.

side_Exception handling mock by effect


>>> a = Mock(spec=A)
>>> a.a_test.side_effect = ValueError
>>> b = B(a)
>>> b.b_test()
error handling

Here, exception handling is mentioned as a common use case, but side_effect is not specialized for exception handling. It is a mechanism for "adding the necessary logic when calling a Mock instance", and it is also possible to add some processing only to a specific argument and use it as a return value as shown below.

Add different processing depending on the argument


>>> class B(object):
    def __init__(self, a_ins):
        self.a_ins = a_ins
    def b_test(self, value):
        try:
            return self.a_ins.a_test(value)
        except ValueError:
            print('error handling')

>>> def handler(value):
    if (value % 2) == 0:
        return value * 2
    return value

>>> a.a_test.side_effect = handler
>>> b = B(a)
>>> b.b_test(1)
1
>>> b.b_test(2)
4
>>> b.b_test(3)
3

However, incorporating too complicated logic into side_effect does not seem to be preferable from a maintenance point of view. If the logic becomes complicated, it may be necessary to take measures such as dividing the test case.

Enable mocking at a specific scope

mock has a mechanism called patch that enables mocking only in a specific scope. patch can be used in function calls, but it can also be treated as a context manager or decorator. In many cases, it will be handled here.

An example of handling as a context manager is as follows. You can handle the mock with the name specified after as.

Treat patch as a context manager


>>> class C(object):
    def hoge(self):
        return 'hoge'

#Class C mock with scope only under the with statement(CMock)Can be used
>>> with patch('__main__.C') as CMock:
    c = CMock()
    c.hoge.return_value = 'test'
    print(c.hoge())
...
test

When handling as a decorator, it is as follows. A mock is passed as the last argument of the decorated function.

Treat patch as a decorator for a function


>>> @patch('__main__.C')
    def patched_func(CMock):
        c = CMock()
        c.hoge.return_value = 'test'
        print(c.hoge())
...
>>> patched_func()
test

It can also be treated as a decorator for a class as well as a decorator for a function. Many test tools have the ability to group test cases into classes (assuming = 1 test case / 1 method), so a common mock can be applied to all tests. At this time, it is necessary to pay attention to the existence of patch.TEST_PREFIX. If you specify a decorator for the class, the mock is passed only for methods that start with patch.TEST_PREFIX. Therefore, it is necessary to implement the test method according to a certain naming convention. (But the default value is'test', so don't you worry too much?)

Treat patch as a decorator for a class


#Mock is passed only to methods with this prefix.
>>> patch.TEST_PREFIX
'test'

>>> @patch('__main__.C')
class CTest(object):
    def test_c(self, CMock):
        c = CMock()
        c.hoge.return_value = 'hoge_test'
        print(c.hoge())

    def notmock(self, CMock):
        pass
...
# 'test'Methods that start with. A mock is handed over.
>>> CTest().test_c()
hoge_test

# 'test'Methods that do not start with. Since the mock is not passed, an error will occur due to insufficient arguments.
>>> CTest().notmock()
Traceback (most recent call last):
  File "<ipython-input-189-cee8cb83c7b4>", line 1, in <module>
    CTest().notmock()
TypeError: notmock() missing 1 required positional argument: 'CMock'

Recommended Posts

A note about mock (Python mock library)
A memorandum about Python mock
A note about [python] __debug__
Python: A Note About Classes 1 "Abstract"
A note about __call__
A note about subprocess
A note about mprotect (2)
A note about the python version of python virtualenv
A memorandum about the Python tesseract wrapper library
A note about KornShell (ksh)
A memorandum about correlation [Python]
A note about TensorFlow Introduction
Use pymol as a python library
Python Note: About comparison using is
A note about hitting the Facebook API with the Python SDK
A note about get_scorer in sklearn
Note: Python
Python note
Generate a Python library download badge
About psd-tools, a library that can process psd files in Python
After researching the Python library, I understood a little about egg.info.
A note where a Python beginner got stuck
A Java programmer studied Python. (About type)
About February 02, 2020 * This is a Python article.
A note about doing the Pyramid tutorial
Python 3.6 email library
About python comprehension
Python study note_002
Note: Python Decorator
Python programming note
[Python] Learning Note 1
Python ast library
About Python tqdm.
About python yield
Python study note_004
Try using APSW, a Python library that SQLite can get serious about
About python inheritance
About python, range ()
Python study note_003
Python Library notes
About python decorators
A note about the functions of the Linux standard library that handles time
[Note] openCV + python
Note about awk
About python reference
About Python decorators
[Python] About multi-process
Python beginner's note
A Java programmer studied Python. (About functions (methods))
A Java programmer studied Python. (About the decorator)
[Note] Create a one-line timezone class with python
A note on optimizing blackbox functions in Python
A memo about writing merge sort in Python
A simple Pub / Sub program note in Python
Python Note: When assigning a value to a string
A story about running Python on PHP on Heroku
Think about building a Python 3 environment in a Mac environment
[Note] About the role of underscore "_" in Python
A note about checking modifiers with Max Plus
A story about modifying Python and adding functions
About Python for loops