I'm having trouble with pytest-mock and mocking open.
The code I wish to test looks like:
import re
import os
def get_uid():
regex = re.compile('Serial\s+:\s*(\w+)')
uid = "NOT_DEFINED"
exists = os.path.isfile('/proc/cpuinfo')
if exists:
with open('/proc/cpuinfo', 'r') as file:
cpu_details = file.read()
uid = regex.search(cpu_details).group(1)
return uid
So the test file is:
import os
import pytest
from cpu_info import uid
@pytest.mark.usefixtures("mocker")
class TestCPUInfo(object):
def test_no_proc_cpuinfo_file(self):
mocker.patch(os.path.isfile).return_value(False)
result = uid.get_uid()
assert result == "NOT_FOUND"
def test_no_cpu_info_in_file(self):
file_data = """
Hardware : BCM2835
Revision : a020d3
"""
mocker.patch('__builtin__.open', mock_open(read_data=file_data))
result = uid.get_uid()
assert result == "NOT_DEFINED"
def test_cpu_info(self):
file_data = """
Hardware : BCM2835
Revision : a020d3
Serial : 00000000e54cf3fa
"""
mocker.patch('__builtin__.open', mock_open(read_data=file_data))
result = uid.get_uid()
assert result == "00000000e54cf3fa"
The test run gives:
pytest
======================================= test session starts ========================================
platform linux -- Python 3.5.3, pytest-4.4.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/robertpostill/software/gateway
plugins: mock-1.10.4
collected 3 items
cpu_info/test_cpu_info.py FFF [100%]
============================================= FAILURES =============================================
______________________________ TestCPUInfo.test_no_proc_cpuingo_file _______________________________
self = <test_cpu_info.TestCPUInfo object at 0x75e6eaf0>
def test_no_proc_cpuingo_file(self):
> mocker.patch(os.path.isfile).return_value(False)
E NameError: name 'mocker' is not defined
cpu_info/test_cpu_info.py:9: NameError
___________________________________ TestCPUInfo.test_no_cpu_info ___________________________________
self = <test_cpu_info.TestCPUInfo object at 0x75e69d70>
def test_no_cpu_info(self):
file_data = """
Hardware : BCM2835
Revision : a020d3
"""
> mocker.patch('__builtin__.open', mock_open(read_data=file_data))
E NameError: name 'mocker' is not defined
cpu_info/test_cpu_info.py:18: NameError
____________________________________ TestCPUInfo.test_cpu_info _____________________________________
self = <test_cpu_info.TestCPUInfo object at 0x75e694f0>
def test_cpu_info(self):
file_data = """
Hardware : BCM2835
Revision : a020d3
Serial : 00000000e54cf3fa
"""
> mocker.patch('__builtin__.open', mock_open(read_data=file_data))
E NameError: name 'mocker' is not defined
cpu_info/test_cpu_info.py:28: NameError
===================================== 3 failed in 0.36 seconds =====================================
I think I've declared the mocker fixture correctly but it would seem not... What am I doing wrong?
There are not that many issues with mock usage in your tests. In fact, there are only two:
mocker
fixtureIf you need to access the return value of a fixture, include its name in the test function arguments, for example:
class TestCPUInfo:
def test_no_proc_cpuinfo_file(self, mocker):
mocker.patch(...)
pytest
will automatically map the test argument value to fixture value when running the tests.
mocker.patch
mocker.patch
is just a shim to unittest.mock.patch
, nothing more; it's there merely for convenience so that you don't have to import unittest.mock.patch
everywhere. This means that mocker.patch
has the same signature as unittest.mock.patch
and you can always consult the stdlib's docs when in doubt of using it correctly.
In you case, mocker.patch(os.path.isfile).return_value(False)
is not a correct usage of patch
method. From the docs:
target should be a string in the form
'package.module.ClassName'
....
patch()
takes arbitrary keyword arguments. These will be passed to theMock
(ornew_callable
) on construction.
This means that the line
mocker.patch(os.path.isfile).return_value(False)
should be
mocker.patch('os.path.isfile', return_value=False)
All that is left now are errors that have something to do with your implementation; you have to either adapt the tests to test the correct behaviour or fix the implementation errors.
Examples:
assert result == "NOT_FOUND"
will always raise because "NOT_FOUND"
isn't even present in the code.
assert result == "NOT_DEFINED"
will always raise because uid = "NOT_DEFINED"
will always be overwritten with regex search result and thus never returned.
Assuming your tests are the single source of truth, I fixed two errors with mock usage described above and adapted the implementation of get_uid()
to make the tests pass:
import os
import re
def get_uid():
regex = re.compile(r'Serial\s+:\s*(\w+)')
exists = os.path.isfile('/proc/cpuinfo')
if not exists:
return 'NOT_FOUND'
with open('/proc/cpuinfo', 'r') as file:
cpu_details = file.read()
match = regex.search(cpu_details)
if match is None:
return 'NOT_DEFINED'
return match.group(1)
Tests:
import pytest
import uid
class TestCPUInfo:
def test_no_proc_cpuinfo_file(self, mocker):
mocker.patch('os.path.isfile', return_value=False)
result = uid.get_uid()
assert result == "NOT_FOUND"
def test_no_cpu_info_in_file(self, mocker):
file_data = """
Hardware : BCM2835
Revision : a020d3
"""
mocker.patch('builtins.open', mocker.mock_open(read_data=file_data))
result = uid.get_uid()
assert result == "NOT_DEFINED"
def test_cpu_info(self, mocker):
file_data = """
Hardware : BCM2835
Revision : a020d3
Serial : 00000000e54cf3fa
"""
mocker.patch('builtins.open', mocker.mock_open(read_data=file_data))
result = uid.get_uid()
assert result == "00000000e54cf3fa"
Note that I'm using Python 3, so I can't patch __builtin__
and resort to patching builtins
; aside from that, the code should be identical to Python 2 variant. Also, since mocker
is used anyway, I used mocker.mock_open
, thus saving me the additional import of unittest.mock.mock_open
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With