Here is a brief introduction to pytest-mock.
If you want to test a module or flask app, you will often write test cases in pytest. But what if your test includes the process of communicating with HTTP and the outside world? You can create an endpoint during testing, or you can create an if branch during testing. However, when creating an endpoint, there is a problem that the backend must be changed, and if it is, the implementation becomes complicated. That's where ** pytest-mock ** comes in.
A thin wrapper for unittest.mock. It is a plug-in library that mock existing libraries and functions (models of existing products), and you can avoid the actual behavior of objects mocked during pytest. For example, if your test was using his HTTP POST library, you can avoid her actual POST operation by replacing it with a mock. If you chew more, it says, "It's as if he POSTs, but he doesn't."
pip install pytest-mock
All you have to do now is call the argument mocker
in pytest and you'll be able to use mocker.
patch
Let's start with a test that doesn't have pytest-mock.
mock_project
├── __init__.py
├── some_file.py
└── tests
├── __init__.py
└── test_some_file.py
some_file.py
from random import random
def generate_random():
return random()
tests/test_some_file.py
import pytest
from mock_project import some_file
class TestA:
def test_01(self):
assert some_file.generate_random() == 1
This will definitely fall. Even if you actually generate numbers randomly, the probability of becoming 1 is extremely small. Then what should we do?
Here we use a mock.
When mocking, you need to specify the object to import to.
Below, mock random
in some_file
with fake_random
.
tests/test_some_file.py
import pytest
import some_file
def fake_random():
return 1
class TestA:
def test_01(self, mocker):
mocker.patch.object(some_file,"random",fake_random)
assert some_file.generate_random() == 1
The actual operation is as follows. (Processed)
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3
tests/test_some_file.py . [100%]
=========== 1 passed in 0.79s ===========
mocker.patch.object
Surprisingly, there is a way to mock using mocker.patch
.
mocker.patch.object
inputs the module in the first argument, while mocker.patch
inputs string.
See the code below.
tests/test_some_file.py
import pytest
from mock_project import some_file
import random
# SAMPLE ONE
def fake_random():
return 1
class TestA:
def test_01(self, mocker):
mocker.patch.object(some_file,"random",fake_random)
assert some_file.generate_random() == 1
def test_02(self, mocker):
mocker.patch("mock_project.some_file.random",fake_random)
assert some_file.generate_random() == 1
When executed, it is as follows.
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3
tests/test_some_file.py .. [100%]
=========== 1 passed in 0.79s ===========
That's as expected.
Both can do the same thing, so which one to use depends on the situation and it's a matter of taste, but if I choose, I'll choose mocker.patch.object
whenever possible.
It is also possible to mock a function with arguments.
some_file.py
def amplify_10(x):
return 10 * x
def process_10(x):
return amplify_10(x)
tests/test_some_file.py
import some_file
import random
def fake_amplify_10(x):
return int(x/10)
class TestB:
def test_01(self, mocker):
mocker.patch.object(some_file,"amplify_10",fake_amplify_10)
assert some_file.process_10(10) == 1
print("amplify test")
When executed, it is as follows.
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3
tests/test_some_file.py . [100%]
=========== 1 passed in 0.60s ===========
It is also possible to mock classes and modules in other files as shown below.
some_file.py
import MeCab
def parse_sent(x):
mecab = MeCab.Tagger("-d /tmp/xxxxx")
return mecab.parse(x).split()
tests/test_some_file.py
class TestC:
def test_01(self, mocker):
mock_MeCab = mocker.Mock()
mock_MeCab_Tagger = mocker.Mock()
def fake_parse(x):
return "a b c"
mock_MeCab_Tagger.parse = fake_parse
mock_MeCab.Tagger = mocker.Mock(return_value=mock_MeCab_Tagger)
mocker.patch.object(some_file,"MeCab",mock_MeCab)
res = some_file.parse_sent("Hello, today")
assert res == ["a","b","c"]
If you also execute this, it will be as follows.
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3
tests/test_some_file.py . [100%]
=========== 1 passed in 0.58s ===========
To explain a little about this, the return_value
of mocker.Mock
that appears here means what to return on call.
x=mocker.Mock(return_value="hi")
print(x("zzzzzzz")) # hi
In other words, if you look at the above classes one by one, it will be as follows.
some_file.py
# MeCab -> mock_MeCab
mecab = mock_MeCab.Tagger("-d /tmp/xxxxx")
# mock_MeCab.Tagger -> mocker.Mock(return_value=mock_MeCab_Tagger) -> mock_instance
mecab = mocker.Mock(return_value=mock_MeCab_Tagger)("-d /tmp/xxxxx") #The second argument is call
# mock_instance("xxx") -> mock_MeCab_Tagger
mecab.parse = mock_MeCab_Tagger.parse
mecab.parse = fake_parse
So some_file.py
does the following:
some_file.py
def parse_sent(x):
return fake_parse(x).split()
I introduced pytest-mock as above. This time we introduced overwriting functions and overwriting modules, but you can also record how many times it was called and what arguments it was called. If you have a chance to use it, please try it.
Recommended Posts