pytest-mocks and declaring a class level fixture - python

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:
Accessing mocker fixture
If 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.
Using 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 the Mock (or new_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)
Discrepancies between tested behaviour and real implementation logic
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.
Working example
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.

Related

Not sure why MyMock.env["key1"].search.side_effect=["a", "b"] works but MyMock.env["key1"] = ["a"] with MyMock.env["key2"] = ["b"] does not work

I had created a simple example to illustrate my issue. First is the setup say mydummy.py:
class TstObj:
def __init__(self, name):
self.name = name
def search(self):
return self.name
MyData = {}
MyData["object1"] = TstObj("object1")
MyData["object2"] = TstObj("object2")
MyData["object3"] = TstObj("object3")
def getObject1Data():
return MyData["object1"].search()
def getObject2Data():
return MyData["object2"].search()
def getObject3Data():
return MyData["object3"].search()
def getExample():
res = f"{getObject1Data()}{getObject2Data()}{getObject3Data()}"
return res
Here is the test that failed.
def test_get_dummy1():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1"]
mydummy.MyData["object2"].search.side_effect = ["obj2"]
mydummy.MyData["object3"].search.side_effect = ["obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
The above failed with run time error:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py:1078: StopIteration
Here is the test that passed:
def test_get_dummy2():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Am I missing something? I would have expected test_get_dummy1() to work and test_get_dummy2() to fail and not vice versa. Where and how can I find/learn more information about mocking to explain what is going on...
MyData["object1"] is converted to this function call: MyData.__getitem__("object1"). When you call your getExample method, the __getitem__ method is called 3 times with 3 parameters ("object1", "object2", "object3").
To mock the behavior you could have written your test like so:
def test_get_dummy_alternative():
mydummy.MyData = MagicMock()
mydummy.MyData.__getitem__.return_value.search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Note the small change from your version: mydummy.MyData["object1"]... became: mydummy.MyData.__getitem__.return_value.... This is the regular MagicMock syntax - we want to to change the return value of the __getitem__ method.
BONUS:
I often struggle with mock syntax and understanding what's happening under the hood. This is why I wrote a helper library: the pytest-mock-generator. It can show you the actual calls made to the mock object.
To use it in your case you could have added this "exploration test":
def test_get_dummy_explore(mg):
mydummy.MyData = MagicMock()
mydummy.getExample()
mg.generate_asserts(mydummy.MyData, name='mydummy.MyData')
When you execute this test, the following output is printed to the console, which contains all the asserts to the actual calls to the mock:
from mock import call
mydummy.MyData.__getitem__.assert_has_calls(calls=[call('object1'),call('object2'),call('object3'),])
mydummy.MyData.__getitem__.return_value.search.assert_has_calls(calls=[call(),call(),call(),])
mydummy.MyData.__getitem__.return_value.search.return_value.__str__.assert_has_calls(calls=[call(),call(),call(),])
You can easily derive from here what has to be mocked.

pytest only executing the last test defined in a module/file

pytest never fails regardless of the assertions being done in first method. The same behavior is observed when I change the order of methods also. It fails only if I change something in the last method's assertions.
from cpu import CPU
#pytest.fixture
def cpu():
return CPU()
def test_00e0(cpu):
cpu.sp = 0xa
cpu.stack[cpu.sp] = 0x220
cpu.pc = 0x200
cpu.i_00ee()
assert cpu.sp == 0x9
assert cpu.pc == 0x220
def test_00e0(cpu):
cpu.display[0][0] = 1
cpu.i_00e0()
assert sum([sum(x) for x in cpu.display]) == 0
assert cpu.draw_flag == True```
Both your test methods have the same name, so the 2nd is overwriting the 1st. Give them different names and you should be OK.

python class method mocking failure

Trying to understand mocking/patching and I have a restful API project with three files (FYI, I'm using flask)
class1.py
domain.py
test_domain.py
class1.py file content:
class one:
def addition(self):
return 4+5
domain.py file content:
from class1 import one
class DomainClass(Resource):
def post(self):
test1 = one()
val = test1.addition()
return {'test' : val }
test_domain.py file content:
import my_app
from flask_api import status
from mock import patch
app = my_app.app.test_client()
def test_post():
with patch('domain.one') as mock:
instance = mock.return_value
instance.addition.return_value = 'yello'
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
assert mock.called
For my test_domain.py file, I've also tried this...
#patch('domain.one')
def test_post(mock_domain):
mock_domain.addition.return_value = 1
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
My assert for the status of 200 passes, however, the problem is that I'm not able to mock or patch the addition method to give me value of 1 in place of 9 (4+5). I also tried doing 'assert mock.called' and it failes as well. I know I should be mocking/patching where the 'one()' method is used, i.e. in domain.py not in class1.py. But I tried even mocking class1.one in place of domain.one and I still kept getting 9 and not 1. What am I doing wrong ?
******** Update
I've another dilemma on the same issue, I tried doing this in the test_domain file instead of patching....
from common.class1 import one
def test_post():
one.addition = MagicMock(return_value=40)
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
Question
In update above, I did not do a mock at the place where it is used (i.e.: domain.one.addition = MagicMock(...) and it still worked !!!! It seems it may be doing a global change. Why did this work ?
In the above example, 'one' is a class in the module class1.py. If I change this class 'one' to a function in class1.py, mocking does not work. It seems this function 'one' residing in module class1.py can not be mocked like this...one.return_value = 'xyz', why? Can it be mocked globally ?
There are some issues in your code. In the first example you forgot that patch() is applied in with context and the original code is recovered when the context end. Follow code should work:
def test_post():
with patch('domain.one') as mock:
instance = mock.return_value
instance.addition.return_value = 'yello'
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
assert mock.called
assert response.data['test'] == 'yello'
The second one have an other issue: if you want patch just addition method you should use:
#patch('domain.one.addition')
def test_post(mock_addition):
mock_addition.return_value = 1
...
assert mock_addition.called
assert response.data['test'] == 1
If you want mock all one class you should set the return value of addition method of mock instance returned by mock_domain call like in your first example:
#patch('domain.one')
def test_post(mock_domain):
mock_addition = mock_domain.return_value.addition
mock_addition.return_value = 1
...
assert mock_addition.called
assert response.data['test'] == 1

Getting Python's nosetests results in a tearDown() method

I want to be able to get the result of a particular test method and output it inside the teardown method, while using the nose test runner.
There is a very good example here.
But unfortunately, running nosetests example.py does not work, since nose doesn't seem to like the fact that the run method in the superclass is being overridden:
AttributeError: 'ResultProxy' object has no attribute 'wasSuccessful'
Caveat: the following doesn't actually access the test during the tearDown, but it does access each result.
You might want to write a nose plugin (see the API documentation here). The method that you are probably interested in is afterTest(), which is run... after the test. :) Though, depending on your exact application, handleError()/handleFailure() or finalize() might actually be more useful.
Here is an example plugin that accesses the result of a test immediately after it is executed.
from nose.plugins import Plugin
import logging
log = logging.getLogger('nose.plugins.testnamer')
class ReportResults(Plugin):
def __init__(self, *args, **kwargs):
super(ReportResults, self).__init__(*args, **kwargs)
self.passes = 0
self.failures = 0
def afterTest(self, test):
if test.passed:
self.passes += 1
else:
self.failures += 1
def finalize(self, result):
print "%d successes, %d failures" % (self.passes, self.failures)
This trivial example merely reports the number of passes and failures (like the link you included, but I'm sure you can extend it to do something more interesting (here's another fun idea). To use this, make sure that it is installed in Nose (or load it into a custom runner), and then activate it with --with-reportresults.
If you are OK with adding some boilerplate code to the tests, something like the following might work.
In MyTest1, tearDown is called at the end of each test, and the value of self.result has been set to a tuple containing the method name and a dictionary (but you could set that to whatever you like). The inspect module is used to get the method name, so tearDown knows which test just ran.
In MyTest2, all the results are saved in a dictionary (results), which you can do with what you like in the tearDownClass method.
import inspect
import unittest
class MyTest1(unittest.TestCase):
result = None
def tearDown(self):
print "tearDown:", self.result
def test_aaa(self):
frame = inspect.currentframe()
name = inspect.getframeinfo(frame).function
del frame
self.result = (name, None)
x = 1 + 1
self.assertEqual(x, 2)
self.result = (name, dict(x=x))
def test_bbb(self):
frame = inspect.currentframe()
name = inspect.getframeinfo(frame).function
del frame
self.result = (name, None)
# Intentional fail.
x = -1
self.assertEqual(x, 0)
self.result = (name, dict(x=x))
class MyTest2(unittest.TestCase):
results = {}
#classmethod
def tearDownClass(cls):
print "tearDownClass:", cls.results
def test_aaa(self):
frame = inspect.currentframe()
name = inspect.getframeinfo(frame).function
del frame
self.results[name] = None
x = 1 + 1
self.assertEqual(x, 2)
self.results[name] = dict(x=x)
def test_bbb(self):
frame = inspect.currentframe()
name = inspect.getframeinfo(frame).function
del frame
self.results[name] = None
x = -1
self.assertEqual(x, 0)
self.results[name] = dict(x=x)
if __name__ == '__main__':
unittest.main()

How to test with Python's unittest that a warning has been thrown?

I have a following function in Python and I want to test with unittest that if the function gets 0 as argument, it throws a warning. I already tried assertRaises, but since I don't raise the warning, that doesn't work.
def isZero(i):
if i != 0:
print "OK"
else:
warning = Warning("the input is 0!")
print warning
return i
Starting with Python 3.2, you can simply use assertWarns() method.
with self.assertWarns(Warning):
do_something()
You can use the catch_warnings context manager. Essentially this allows you to mock the warnings handler, so that you can verify details of the warning. See the official docs for a fuller explanation and sample test code.
import warnings
def fxn():
warnings.warn("deprecated", DeprecationWarning)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Trigger a warning.
fxn()
# Verify some things
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated" in str(w[-1].message)
You can write your own assertWarns function to incapsulate catch_warnings context. I've just implemented it the following way, with a mixin:
class WarningTestMixin(object):
'A test which checks if the specified warning was raised'
def assertWarns(self, warning, callable, *args, **kwds):
with warnings.catch_warnings(record=True) as warning_list:
warnings.simplefilter('always')
result = callable(*args, **kwds)
self.assertTrue(any(item.category == warning for item in warning_list))
A usage example:
class SomeTest(WarningTestMixin, TestCase):
'Your testcase'
def test_something(self):
self.assertWarns(
UserWarning,
your_function_which_issues_a_warning,
5, 10, 'john', # args
foo='bar' # kwargs
)
The test will pass if at least one of the warnings issued by your_function is of type UserWarning.
#ire_and_curses' answer is quite useful and, I think, canonical. Here is another way to do the same thing. This one requires Michael Foord's excellent Mock library.
import unittest, warnings
from mock import patch_object
def isZero( i):
if i != 0:
print "OK"
else:
warnings.warn( "the input is 0!")
return i
class Foo(unittest.TestCase):
#patch_object(warnings, 'warn')
def test_is_zero_raises_warning(self, mock_warn):
isZero(0)
self.assertTrue(mock_warn.called)
if __name__ == '__main__':
unittest.main()
The nifty patch_object lets you mock out the warn method.
One problem with the warnings.catch_warnings approach is that warnings produced in different tests can interact in strange ways through global state kept in __warningregistry__ attributes.
To address this, we should clear the __warningregistry__ attribute of every module before every test that checks warnings.
class MyTest(unittest.TestCase):
def setUp(self):
# The __warningregistry__'s need to be in a pristine state for tests
# to work properly.
for v in sys.modules.values():
if getattr(v, '__warningregistry__', None):
v.__warningregistry__ = {}
def test_something(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", MySpecialWarning)
...
self.assertEqual(len(w), 1)
self.assertIsInstance(w[0].message, MySpecialWarning)
This is how Python 3's assertWarns() method is implemented.
Building off the answer from #ire_and_curses,
class AssertWarns(warnings.catch_warnings):
"""A Python 2 compatible version of `unittest.TestCase.assertWarns`."""
def __init__(self, test_case, warning_type):
self.test_case = test_case
self.warning_type = warning_type
self.log = None
super(AssertWarns, self).__init__(record=True, module=None)
def __enter__(self):
self.log = super(AssertWarns, self).__enter__()
return self.log
def __exit__(self, *exc_info):
super(AssertWarns, self).__exit__(*exc_info)
self.test_case.assertEqual(type(self.log[0]), self.warning_type)
This can be called similarly to unittest.TestCase.assertWarns:
with AssertWarns(self, warnings.WarningMessage):
warnings.warn('test warning!')
where self is a unittest.TestCase.
Per Melebius' answer, you can use self.assertWarns().
Additionally, if you want to check the warning message as well, you can use self.assertWarnsRegex() for that greater specificity:
import warnings
from unittest import TestCase
class MyCustomWarning(Warning):
...
def is_zero(i: int) -> int:
if i != 0:
print("OK")
else:
warnings.warn("the input is 0!", MyCustomWarning)
return i
class TestIsZero(TestCase):
def test_when_then_input_is_zero(self):
regex = "the input is 0"
with self.assertWarnsRegex(MyCustomWarning, expected_regex=regex):
_ = is_zero(0)
This test will fail if the regex is not found in the warning message.

Categories