Mocks not getting hit in Python unit tests - python

I'm new to Python, but I've done quite a bit of unit testing in C# and JavaScript. I'm having trouble figuring out the mocking framework in Python. Here's what I have (trimmed down):
invoice_business.py
import ims.repository.invoice_repository as invoiceRepository
import logging
logger = logging.getLogger(__name__)
def update_invoice_statuses(invoices):
for invoice in invoices:
dbInvoice = invoiceRepository.get(invoice.invoice_id)
print("dbInvoice is %s" % dbInvoice) #prints <MagicMock etc.>
if dbInvoice is None:
logger.error("Unable to update status for invoice %d" % invoice.invoice_id)
continue;
test_invoice_business.py
from unittest import TestCase, mock
import logging
import ims.business.invoice_business as business
class UpdateInvoiceTests(TestCase):
#mock.patch("ims.business.invoice_business.invoiceRepository")
#mock.patch("ims.business.invoice_business.logger")
def test_invoiceDoesNotExist_logsErrorAndContinues(self, invoiceRepoMock, loggerMock):
#Arrange
invoice = Invoice(123)
invoice.set_status(InvoiceStatus.Filed, None)
invoiceRepoMock.get.return_value(33)
#Act
business.update_invoice_statuses([invoice])
#Assert
invoiceRepoMock.get.assert_called_once_with(123)
loggerMock.error.assert_called_once_with("Unable to update status for invoice 123")
The test fails with
AssertionError: Expected 'get' to be called once. Called 0 times.
The print statement in update_invoice_statuses gets hit, though, because I see the output of
dbInvoice is <MagicMock name='invoiceRepository.get()' id='xxxx'>
Any idea what I'm doing wrong here?
Edit: After #chepner's help, I ran into another assertion error and realized it was because I should be using invoiceRepoMock.get.return_value = None rather than .return_value(None)

The mock arguments to your test function are swapped. The inner decorator (for the logger) is applied first, so the mock logger should be the first argument to your method.
#mock.patch("ims.business.invoice_business.invoiceRepository")
#mock.patch("ims.business.invoice_business.logger")
def test_invoiceDoesNotExist_logsErrorAndContinues(self, loggerMock, invoiceRepoMock):
...

Related

pytest : How to write pytest code to detect a "func" called without actually executing the "func"

pytest : How to write pytest code to detect a "func" called without actually executing the "func" ,
target.py:
import requests
import threading
CFG=None
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for funct in set(targets):
threading.Thread(target=targets[funct], args=(CFG[url],)).start()
#add_target
def child_func(url):
try:
response = requests.request("POST", url,
headers={"Content-Type": "application/json"},
data="{\"text\": \"target py test\"}")
response.raise_for_status()
except Exception as err:
raise RuntimeError(err)
test_target.py:
During testing, I want child_func() to get called but the child_func() body should not get executed. Rather, after running tests, child_func() gets executed & test run results in "AssertionError: False is not true "
from unittest.mock import Mock, patch
import target
# Third-party imports...
from nose.tools import assert_true, assert_is_not_none
target.CFG={'url':"some valid url"}
#patch('target.child_func')
def test_child_func(mock_child_func):
parent_func("url")
assert_true(mock_child_func.called)
First (for the sake of my own sanity) let's whittle this down to an actual MRE -- we don't actually need threading or requests or any of the implementation details of child_func to demonstrate the problem here.
target.py:
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for func in targets.values():
func(url)
#add_target
def child_func(url):
raise NotImplementedError("The test shouldn't actually call this function!")
test_target.py:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.child_func')
def test_parent_func(mock_child_func):
parent_func("url")
assert mock_child_func.call_count == 1
Within our test, we want parent_func to call mock_child_func, not the real child_func. But when we run our test, we can quickly see that our patch didn't work:
============================================== short test summary info ===============================================
FAILED test_target.py::test_parent_func - NotImplementedError: The test shouldn't actually call this function!
The reason for this is that parent_func doesn't call child_func, it calls targets['child_func']. Patching 'target.child_func' doesn't modify the actual function, it modifies what the name child_func points to in the target module -- and that name isn't in use in the code under test!
The easiest fix here is to patch target.targets:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.targets', new_callable=dict)
def test_parent_func(mock_targets):
mock_targets['child_func'] = Mock()
parent_func("url")
assert mock_targets['child_func'].call_count == 1
Now our test passes because when parent_func iterates through its targets, it gets our mock_targets dict, which we can populate with whatever functions/mocks we want.

Replacing an object with mocks

I'm not sure what I'm doing wrong. Perhaps I have the wrong end of the stick with mocking. But my assumption was that when you use mocks it basically does some magic and replaces objects in your original code.
sites.py
class Sites:
def __init__(self):
pass
def get_sites(self):
return ['washington', 'new york']
my_module.py
from mylib import sites
def get_messages():
# get Sites
sm = sites.Sites()
sites = sm.get_sites()
print('Sites:' , sites)
for site in sites:
print('Test: ' , site)
my_test.py
import my_module
import unittest
from unittest.mock import patch
class MyModuleTestCase(unittest.TestCase):
#patch('my_module.Sites')
def test_process_the_queue(self, mock_sites):
mock_sites.get_sites.return_value = ['london', 'york']
print(mock_sites.get_sites())
my_module.get_messages()
if __name__ == '__main__':
unittest.main()
Running this I get the following output:
.['london', 'york']
Sites: <MagicMock name='Sites().get_sites()' id='139788231189504'>
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
[Finished in 0.1s]
I was expecting the second print output (which occurs within my_module.py) to be the same as the first and to loop through the list I passed through as a return value.
Any help would be greatly appreciated.
Updated
To show how I was originally importing my class
Python mock, while silly powerful, is definitely not very intuitive to use.
The print statement shows that you are patching my_module.Sites correctly but you have not registered the get_sites return value correctly, and it should be:
mock_sites.return_value.get_sites.return_value = ['london', 'york']
The print statement shows that there was a call to Sites().get_sites() registered on your patched object:
Sites: <MagicMock name='Sites().get_sites()' id='139788231189504'>
When reading this I find it helpful to translate () to return_value
Sites.return_value.get_sites.return_value
The return value you are missing represents the instantiation of the mock sites object: Sites().
The problem I was having was with the way that I was importing and calling my external class.
from mylib import sites
sm = sites.Sites()
Mock is much happier when you use:
from mylib.sites import Sites
sm = Sites()
This along with dm03514's answer helped me to get it working

Mock an instance method with a certain return value?

I have a method under test that looks like this:
def execute_update(self):
"""Execute an update."""
p = subprocess.Popen(['command'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
stdout, stderr = p.communicate()
if p.returncode == 0:
# successful update, notify
self.logger.info("Successfully updated.")
else:
# failed update, notify
self.logger.error("Unable to update (code {returncode}):\n{output}".format(
returncode=p.returncode, output=stdout))
except KeyboardInterrupt as e:
# terminate the process
p.terminate()
raise e
I'm attempting to test its invocation of Popen and its invocation of the logging functions in a unittest.TestCase test method with mock:
#mock.patch.object(subprocess.Popen, 'communicate', autospec=True)
#mock.patch('updateservice.subprocess.Popen', autospec=True)
def test_fetch_metadata(self, mock_popen, mock_communicate):
"""Test that we can fetch metadata correctly."""
mock_communicate.return_value = ("OUT", "ERR")
mock_popen.returncode = 0
self.reference.execute_update()
# other asserts
The last line fails with:
stdout, stderr = p.communicate()
ValueError: need more than 0 values to unpack
What am I doing wrong? I have the following requirements:
Test that the constructor to subprocess.Popen was called with the right values.
Test that the logging calls are running with the output and return code from the process.
Number two is easy enough, I'm just injecting a MagicMock as the logger object, but I"m having trouble with number one.
I think the main problem is coming from your patch object here:
#mock.patch.object(subprocess.Popen, 'communicate', autospec=True)
Oddly enough, it seems like the type of mock that is being created is:
<class 'unittest.mock.NonCallableMagicMock'>
This is the first time I have come across a NonCallableMagicMock type before, but looking at the minimal information I found on this, the docs specify this:
The part that raises a flag for me is here:
with the exception of return_value and side_effect which have no
meaning on a non-callable mock.
It would require further investigation to determine what that exactly means. Taking that in to consideration, and maybe you have tried this already, the following change to your unittest yields successful mocking results:
#mock.patch('server.upd.subprocess.Popen', autospec=True)
def test_fetch_metadata(self, mock_popen):
"""Test that we can fetch metadata correctly."""
mock_popen.return_value = Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.return_value = ("OUT", "ERR")
mock_popen_obj.returncode = 0
self.reference.execute_update()
So, as you can see, we are pretty much creating our mock object per the mock_popen.return_value. From there everything else is pretty much aligned with what you were doing.

Testing methods each with a different setup/teardown

I'm testing a class, with many test methods. However, each method has a unique context. I then write my code as following:
class TestSomeClass(unittest.TestCase):
def test_a():
with a_context() as c:
pass
def test_b():
with b_context() as c:
pass
def test_c():
with c_context() as c:
pass
However, the context managers are irrelevant to the test case, and produce temporary files. So as to not pollute the file system when the test fails, I would like to use each context manager in a setup/teardown scenario.
I've looked at nose's with_setup, but the docs say that is meant for functions only, not methods. Another way is to move the test methods to separate classes each with a setup/teardown function. What's a good way to do this?
First of all, I'm not sure why what you have isn't working. I wrote some test code, and it shows that the exit code always gets called, under the unittest.main() execution environment. (Note, I did not test nose, so maybe that's why I couldn't replicate your failure.) Maybe your context manager is broken?
Here's my test:
import unittest
import contextlib
import sys
#contextlib.contextmanager
def context_mgr():
print "setting up context"
try:
yield
finally:
print "tearing down context"
class TestSomeClass(unittest.TestCase):
def test_normal(self):
with context_mgr() as c:
print "normal task"
def test_raise(self):
with context_mgr() as c:
print "raise task"
raise RuntimeError
def test_exit(self):
with context_mgr() as c:
print "exit task"
sys.exit(1)
if __name__ == '__main__':
unittest.main()
By running that with $ python test_test.py I see tearing down context for all 3 tests.
Anyway, to answer your question, if you want a separate setup and teardown for each test, then you need to put each test in its own class. You can set up a parent class to do most of the work for you, so there isn't too much extra boilerplate:
class TestClassParent(unittest.TestCase):
context_guard = context_mgr()
def setUp(self):
#do common setup tasks here
self.c = self.context_guard.__enter__()
def tearDown(self):
#do common teardown tasks here
self.context_guard.__exit__(None,None,None)
class TestA(TestClassParent):
context_guard = context_mgr('A')
def test_normal(self):
print "task A"
class TestB(TestClassParent):
context_guard = context_mgr('B')
def test_normal(self):
print "task B"
This produces the output:
$ python test_test.py
setting up context: A
task A
tearing down context: A
.setting up context: B
task B
tearing down context: B
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK

Strange behaviour mock’s side_effect function

I have this piece of code:
import postie
def send_mail(self, outbox):
try:
postie.postmail(self.dbname, outbox)
except postie.EmailDeliveryException:
self.logger.error('Fail to send mail’)
return False
return True
And I want to test the case a postie.EmailDeliveryException is raised.
So I mock out postie.postmail and put the exception as aside effect of its call:
import postie
#patch('postie.postmail')
def test_send_mail_False(self, postie_stub):
''' Mail send failed '''
postie_stub.return_value = None
postie_stub.side_effect = postie.EmailDeliveryException
op = OutboxProcessor('db name', None, None)
self.assertFalse(op.send_mail(Outbox()))
The above results in:
test_send_mail_False (test_outbox_processor.OutboxProcessorTestCase)
Mail send failed ... No handlers could be found for logger "outbox"
ok
Now I want to mock out the logger and check that the error function is also called in case of ‘EmailDeliveryException’. So I go:
#patch('postie.postmail')
#patch.object(Logger, 'error')
def test_send_mail_False(self, postie_stub, logger_stub):
''' Mail sending failed '''
postie_stub.return_value = None
postie_stub.side_effect = postie.EmailDeliveryException
logger_stub.return_value = None
op = OutboxProcessor('db name', None, None)
self.assertFalse(op.send_mail(Outbox(), None))
logger_stub.assert_called()
The result will be:
FAIL: test_send_mail_False (test_outbox_processor.OutboxProcessorTestCase)
Mail sending failed
AssertionError: True is not false
So it looks like the assertFalse does no longer succeed, (probably the exception is no longer raised). Anyone has any idea if anything interferes with my side_effect here? Thank you in advance!
You have incorrect order of patch decorators (or stub arguments). Here's an explanation from mock docs:
When you nest patch decorators the mocks are passed in to the
decorated function in the same order they applied (the normal python
order that decorators are applied). This means from the bottom up...
So it should be:
#patch.object(Logger, 'error')
#patch('postie.postmail')
def test_send_mail_False(self, postie_stub, logger_stub):
''' Mail sending failed '''

Categories