Why I cannot effectively mock function with unittest.mock.patch? - python

I'm trying to write UT for function with other function mocked. Both lies in the same module. It appears that unittest.mock.patch() call passes without error but doesn't mock anything so I'm getting exception in the example code. So, my question is: How to mock foo with unittest.mock.patch ?
import unittest
import unittest.mock
def foo():
raise Exception("not implemented")
def bar():
foo()
class UT(unittest.TestCase):
def testBar(self):
with unittest.mock.patch('mock_test.foo',spec=True) as mock_foo:
bar() # <- original foo is called here
unittest.main()

Here is a way to use unittest.mock.patch and patch the foo returns. See the comments in the example:
import unittest
from unittest.mock import patch
def foo():
raise Exception("Not implemented")
def bar():
return foo()
class UT(unittest.TestCase):
def testBar(self):
# foo is mocked to raise a ValueError and catch it during the test
with patch('__main__.foo', side_effect=ValueError):
with self.assertRaises(ValueError):
bar()
# foo is mocked to raise and exception and catch it during the test
with patch('__main__.foo', side_effect=Exception):
with self.assertRaises(Exception):
bar()
# foo is mocked to return 1
with patch('__main__.foo', return_value=1):
self.assertEqual(bar(), 1, "Should be equal to 1")
# foo is mocked to return an object
with patch('__main__.foo', return_value={}):
self.assertIsInstance(bar(), dict, "Should be an instance of dict")
unittest.main()
Output:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
For more informations, please visit the official documentation

Related

Unit test object construction that raises ValueError

I have a class which raises ValueError for a non-existent file path constructor parameter and I'd like to test it.
import os
import os.path
class MetricsManager:
def __init__(self, reference_file_path, translations_file_path):
if not os.path.isfile(reference_file_path):
raise ValueError(f'{reference_file_path} does not exist.')
if not os.path.isfile(translations_file_path):
raise ValueError(f'{translations_file_path} does not exist')
self._references_file_path = reference_file_path
self.translations_file_path = translations_file_path
But my test below fails.
import unittest
from src.metrics_manager import MetricsManager
def create_instance():
x = MetricsManager('foo.txt', 'bar.txt')
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertRaises(ValueError, create_instance())
if __name__ == '__main__':
unittest.main()
I did not have the assertRaises() call coded properly:
self.assertRaises(ValueError, MetricsManager, 'foo.txt', 'bar.txt')
assertRaises takes the function to be called as an argument, not the return value from actually calling it.
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertRaises(ValueError, create_instance)
If you call it first, the exception occurs before assertRaises gets called. You have to let assertRaises make the actual call.
You can also use assertRaises in a with statement:
class MyTestCase(unittest.TestCase):
def test_something(self):
with self.assertRaises(ValueError):
create_instance()
Here, you do call create_instance yourself, because any exception raised inside the with statement is implicitly caught and passed to the context manager's __exit__ method.

Unittest for IOError exception handling

Given this code:
try:
#do something
except IOError as message:
logging.error(message)
raise message
I want to test the exception handling part in order to have full coverage.
In the unittest I've tried with:
with patch(new=Mock(side_effect=IOError(errno.EIO))):
self.assertRaises(IOError)
but it doesnt work.
Is this approach correct?
Actually you need to start the Mock so that the side_effectstarts, for example the following:
class Test(unittest.TestCase):
def test(self):
mock = m.Mock()
mock.side_effect = Exception("Big badaboum")
self.assertRaises(Exception, mock)
self.assertRaises can take a callable as second argument, making it equivalent to:
class Test(unittest.TestCase):
def test(self):
mock = m.Mock()
mock.side_effect = Exception("Big badaboum")
with self.assertRaises(Exception):
mock()
And if you want to use it in a test with patch, you can do the following:
import unittest.mock as m
import unittest
def raise_error():
try:
print("Hello") #placeholder for the try clause
except Exception as e:
print(e) #placeholder for the exceptclause
class Test(unittest.TestCase):
#m.patch("__main__.raise_error", side_effect=Exception("Big badaboum")) #replace __main__ by the name of the module with your function
def test(self, mock):
with self.assertRaises(Exception):
mock()
unittest.main()
Edit: And to test the raise of an error inside an except block you need to mock a function call inside the try block you wrote, for instance:
import unittest.mock as m
import unittest
def do_sthing():
print("Hello")
def raise_error():
try:
do_sthing() #this call can be mocked to raise an IOError
except IOError as e:
print(e.strerror)
raise ValueError("Another one")
class Test(unittest.TestCase):
def test(self):
with m.patch("__main__.do_sthing", side_effect=IOError("IOError")):
self.assertRaises(ValueError, raise_error)
unittest.main()
You can use the decorator syntax as well (just putting the test above rewritten to spare some CPU cycle):
class Test(unittest.TestCase):
#m.patch("__main__.do_sthing",side_effect=IOError("IOError"))
def test(self, mock):
self.assertRaises(ValueError, raise_error)

How to mock a property

I'm asking how to mock a class property in a unit test using Python 3. I've tried the following, which makes sense for me following the docs, but it doesn't work:
foo.py:
class Foo():
#property
def bar(self):
return 'foobar'
def test_foo_bar(mocker):
foo = Foo()
mocker.patch.object(foo, 'bar', new_callable=mocker.PropertyMock)
print(foo.bar)
I've installed pytest and pytest_mock and run the test like this:
pytest foo.py
I got the following error:
> setattr(self.target, self.attribute, new_attr)
E AttributeError: can't set attribute
/usr/lib/python3.5/unittest/mock.py:1312: AttributeError
My expectation would be that the test runs without errors.
The property mechanism relies on the property attribute being defined on the object's class. You can't create a "property like" method or attribute on a single instance of a class (for a better understanding, read about Python's descriptor protocol)
Therefore you have to apply the patch to your class - you can use the with statement so that the class is properly restored after your test:
def test_foo_bar(mock):
foo = Foo()
with mock.patch(__name__ + "Foo.bar", new=mocker.PropertyMock)
print(foo.bar)
You can directly return the value if you do not need extra features
from mock import patch
#patch('foo.Foo.bar', 'mocked_property_value')
def test_foo_bar():
foo = Foo()
print(foo.bar)
Or you can wrap MagicMocks with a call to function property:
from mock import patch, MagicMock
#patch('foo.Foo.bar', property(MagicMock(return_value='mocked_property_value')))
def test_foo_bar():
foo = Foo()
print(foo.bar)

Python assertRaises on user-defined exceptions

The following question was triggered by the discussion in this post.
Assume two files (foobar.py and foobar_unittest.py). File foobar.py contains a class (FooBar) with two functions (foo and bar). Function bar raises a built-in exception, function foo a user-defined exception.
# foobar.py
class MyException(Exception):
pass
class FooBar:
def __init__(self):
pass
def bar(self):
raise ValueError('Hello World.')
def foo(self):
raise MyException('Hello World.')
.
# foobar_unittest.py
import unittest
import foobar as fb
class MyException(Exception):
pass
class FooBarTestCases(unittest.TestCase):
def test_bar(self):
with self.assertRaises(ValueError):
fb.FooBar().bar()
def test_foo(self):
with self.assertRaises(MyException):
fb.FooBar().foo()
if __name__ == '__main__':
unittest.main()
When running unit-test on foobar.py, why does the function raising the user-defined exception (foo) fail to pass the test?
>>> python2.7 foobar_unittest.py
.E
======================================================================
ERROR: test_foo (__main__.FooBarTestCases)
----------------------------------------------------------------------
Traceback (most recent call last):
File "foobar_unittest.py", line 11, in test_foo
fb.FooBar().foo()
File "/a_path/foobar.py", line 9, in foo
raise MyException('Hello World.')
MyException: Hello World.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)
import MyException from foobar, don't redefine it.
import unittest
from foobar import MyException
import foobar as fb
class FooBarTestCases(unittest.TestCase):
def test_bar(self):
with self.assertRaises(ValueError):
fb.FooBar().bar()
def test_foo(self):
with self.assertRaises(MyException):
fb.FooBar().foo()
if __name__ == '__main__':
unittest.main()
This code should work now as
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
Be aware that the same happens (happened to me) if you use reload to import your exceptions.
In my unittests I have the relevant imports like
from importlib import reload
import foobar
reload(foobar)
from foobar import MyException
This does not work, too, for whatever reason. Writing this just as
from foobar import MyException
will work. Then, of course, you have to reload the modules yourself.
In case you wonder why I am using reload: How do I unload (reload) a Python module?.

Mocking a function to raise an Exception to test an except block

I have a function (foo) which calls another function (bar). If invoking bar() raises an HttpError, I want to handle it specially if the status code is 404, otherwise re-raise.
I am trying to write some unit tests around this foo function, mocking out the call to bar(). Unfortunately, I am unable to get the mocked call to bar() to raise an Exception which is caught by my except block.
Here is my code which illustrates my problem:
import unittest
import mock
from apiclient.errors import HttpError
class FooTests(unittest.TestCase):
#mock.patch('my_tests.bar')
def test_foo_shouldReturnResultOfBar_whenBarSucceeds(self, barMock):
barMock.return_value = True
result = foo()
self.assertTrue(result) # passes
#mock.patch('my_tests.bar')
def test_foo_shouldReturnNone_whenBarRaiseHttpError404(self, barMock):
barMock.side_effect = HttpError(mock.Mock(return_value={'status': 404}), 'not found')
result = foo()
self.assertIsNone(result) # fails, test raises HttpError
#mock.patch('my_tests.bar')
def test_foo_shouldRaiseHttpError_whenBarRaiseHttpErrorNot404(self, barMock):
barMock.side_effect = HttpError(mock.Mock(return_value={'status': 500}), 'error')
with self.assertRaises(HttpError): # passes
foo()
def foo():
try:
result = bar()
return result
except HttpError as error:
if error.resp.status == 404:
print '404 - %s' % error.message
return None
raise
def bar():
raise NotImplementedError()
I followed the Mock docs which say that you should set the side_effect of a Mock instance to an Exception class to have the mocked function raise the error.
I also looked at some other related StackOverflow Q&As, and it looks like I am doing the same thing they are doing to cause and Exception to be raised by their mock.
https://stackoverflow.com/a/10310532/346561
How to use Python Mock to raise an exception - but with Errno set to a given value
Why is setting the side_effect of barMock not causing the expected Exception to be raised? If I am doing something weird, how should I go about testing logic in my except block?
Your mock is raising the exception just fine, but the error.resp.status value is missing. Rather than use return_value, just tell Mock that status is an attribute:
barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
Additional keyword arguments to Mock() are set as attributes on the resulting object.
I put your foo and bar definitions in a my_tests module, added in the HttpError class so I could use it too, and your test then can be ran to success:
>>> from my_tests import foo, HttpError
>>> import mock
>>> with mock.patch('my_tests.bar') as barMock:
... barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
... result = my_test.foo()
...
404 -
>>> result is None
True
You can even see the print '404 - %s' % error.message line run, but I think you wanted to use error.content there instead; that's the attribute HttpError() sets from the second argument, at any rate.

Categories