This article is brought with ❤ to you by Semaphore.
Post originally published on http://krzysztofzuraw.com/. Republished with author's permission.
Introduction
In this post I will look into the essential part of testing — mocks.
First of all, what I want to accomplish here is to give you basic examples of how to mock data using two tools — mock and pytest monkeypatch.
Why bother mocking?
Some of the parts of our application may have dependencies for other libraries or objects. To isolate the behaviour of our parts, we need to substitute external dependencies. Here comes the mocking. We mock an external API to check certain behaviours, such as proper return values, that we previously defined.
Mocking function
Let’s say we have a module called function.py
:
defsquare(value):returnvalue**2defcube(value):returnvalue**3defmain(value):returnsquare(value)+cube(value)
Then let’s see how these functions are mocked using the mock
library:
try:importmockexceptImportError:fromunittestimportmockimportunittestfromfunctionimportsquare,mainclassTestNotMockedFunction(unittest.TestCase):@mock.patch('__main__.square',return_value=1)deftest_function(self,mocked_square):# because you need to patch in exact place where function that has to be mocked is calledself.assertEquals(square(5),1)@mock.patch('function.square')@mock.patch('function.cube')deftest_main_function(self,mocked_square,mocked_cube):# underling function are mocks so calling main(5) will return mockmocked_square.return_value=1mocked_cube.return_value=0self.assertEquals(main(5),1)mocked_square.assert_called_once_with(5)mocked_cube.assert_called_once_with(5)if__name__=='__main__':unittest.main()
What is happening here? Lines 1-4 are for making this code compatible between Python 2 and 3. In Python 3, mock is part of
the standard library, whereas in Python 2 you need to install it by pip install mock
.
In line 13, I patched the square
function. You have to remember to patch it in the same place you use it. For instance,
I’m calling square(5)
in the test itself so I need to patch it in __main__
. This is the case if I’m running this by
using python tests/test_function.py
. If I’m using pytest for that, I need to
patch it as test_function.square
.
In lines 18-19, I patch the square
and cube
functions in their module because they are used in the main
function.
The last two asserts come from the mock library, and are there to make sure that mock was called with proper values.
The same can be accomplished using mokeypatching
for py.test:
fromfunctionimportsquare,maindeftest_function(monkeypatch):monkeypatch.setattr(“test_function_pytest.square”,lambdax:1)assertsquare(5)==1deftest_main_function(monkeypatch):monkeypatch.setattr(‘function.square’,lambdax:1)monkeypatch.setattr(‘function.cube’,lambdax:0)assertmain(5)==1
As you can see, I’m using monkeypatch.setattr
for setting up a return value for given functions. I still need to
monkeypatch it in proper places — test_function_pytest
and function
.
Mocking classes
I have a module called square
:
importmathclassSquare(object):def__init__(radius):self.radius=radiusdefcalculate_area(self):returnmath.sqrt(self.radius)*math.pi
and mocks using standard lib:
try:importmockexceptImportError:fromunittestimportmockimportunittestfromsquareimportSquareclassTestClass(unittest.TestCase):@mock.patch('__main__.Square')# depends in witch from is rundeftest_mocking_instance(self,mocked_instance):mocked_instance=mocked_instance.return_valuemocked_instance.calculate_area.return_value=1sq=Square(100)self.assertEquals(sq.calculate_area(),1)deftest_mocking_classes(self):sq=Squaresq.calculate_area=mock.MagicMock(return_value=1)self.assertEquals(sq.calculate_area(),1)@mock.patch.object(Square,'calculate_area')deftest_mocking_class_methods(self,mocked_method):mocked_method.return_value=20self.assertEquals(Square.calculate_area(),20)if__name__==‘__main__’:unittest.main()
At line 13, I patch the class Square
.
Lines 15 and 16 present a mocking instance. mocked_instance
is a mock object which returns another mock by default,
and to these mock.calculate_area
I add return_value
1. In line 23, I’m using
MagicMock
, which is a normal mock class, except in that it also retrieves magic methods from the given object. Lastly,
I use patch.object
to mock the method in the Square
class.
The same using pytest:
try:frommockimportMagicMockexceptImportError:fromunittest.mockimportMagicMockfromsquareimportSquaredeftest_mocking_class_methods(monkeypatch):monkeypatch.setattr('test_class_pytest.Square.calculate_area',lambda:1)assertSquare.calculate_area()==1deftest_mocking_classes(monkeypatch):monkeypatch.setattr('test_class_pytest.Square',MagicMock(Square))sq=Squaresq.calculate_area.return_value=1assertsq.calculate_area()==1
The issue here is with test_mocking_class_methods
, which works well in Python 3, but not in Python 2.
All examples can be found in this repo.
If you have any questions and comments, feel free to leave them in the section below.
References:
This article is brought with ❤ to you by Semaphore.