Python Mock: Raising Error for function of Mocked Class - python

I'm trying to test some code that create or changes directories and files based on some inputs. My issue is raising exceptions when a method of a mocked object is called. For example, say I have some code:
def create_if_not_exists(dest):
dir = os.path.dirname(dest)
if not dir:
# create if it doesn't exist
try:
os.makedirs(os.path.dirname(dest))
except OSError:
pass # Nevermind what goes here, it's unimportant to the question
and a unit test:
#patch('my.package.os')
def test_create_dir_if_not_exists(self, mock_os):
mock_os.path.dirname.return_value = None
mock_os.makedirs.raiseError.side_effect = OSError()
with self.assertRaises(OSError)
create_if_not_exists('test')
This setup returns AssertionError: OSError not raised but my understanding is it should raise the error when the makedirs call is made in the actual (non-test) method. Is my understanding incorrect?

Try with the following patch in the test file:
#patch('my.package.os.path.dirname')
#patch('my.package.os.makedirs')
def test_create_dir_if_not_exists(self, mock_os_makedirs, mock_os_path_dirname):
mock_os_path_dirname.return_value = None
mock_os_makedirs.raiseError.side_effect = OSError()
with self.assertRaises(OSError)
create_if_not_exists('test')

Related

Assertion Error when logging.exception(error)

I have this function in a script called mymodule.py
import logging
def foo():
try:
raise ConnectionError('My Connection Error')
except ConnectionError as ce:
logging.exception(ce)
And I have the test for it called test_mymodule.py:
import unittest
import unittest.mock as um
import mymodule
class TestLoggingException(unittest.TestCase):
#um.patch('mymodule.logging')
def test_connection_error_correctly_logged_without_raising(self, mock_logging):
mymodule.foo()
mock_logging.assert_has_calls(
[um.call(ConnectionError('My Connection Error'))]
)
However, when running test_mymodule.py, the below assertion error is raised.
AssertionError: Calls not found.
Expected: [call(ConnectionError('My Connection Error'))]
Actual: [call(ConnectionError('My Connection Error'))]
Why is it thinking they are different and how could I work around this?
The problem is that two instances of ConnectionError, even if create with the same arguments, are not equal.
You create two instances in your code : in foo and in the um.call().
However, those two instance are not the same, and are therefore not equal. You can illustrate that simply:
>>> ConnectionError("test") == ConnectionError("test")
False
One solution is to check which calls were made to the mockup. The calls are exposed through a variable called mockup_calls.
Something like this
class TestLoggingException(unittest.TestCase):
#um.patch('mymodule.logging')
def test_connection_error_correctly_logged_without_raising(self, mock_logging):
mymodule.foo()
print("Calls are: ", mock_logging.mock_calls)
# Check that logging was called with logging.exception(ConnectionError("My Connection Error"))
calls = mock_logging.mock_calls
assert(len(calls) == 1)
# Unpack call
function_called, args, kwargs = calls[0]
assert(function_called == "exception")
connection_error = args[0]
assert(isinstance(connection_error, ConnectionError))
assert(connection_error.args[0] == "My Connection Error") # This will depend on how ConnectionError is defined
What is tricky about this example is that it would work with types that evaluate equal even if they are not the same, like str("hi" == "hi" will yield True), but not most classes.
Does it help ?

Control Flow issue: Python function called but not executed

I have the strangest problem I have ever met in my life.
I have a part of my code that looks like this:
class AzureDevOpsServiceError(Exception):
pass
skip = ["auto"]
def retrieve_results():
print(variable_not_defined)
... # some useful implementation
if not "results" in skip:
try:
print("before")
retrieve_results()
print("after")
except AzureDevOpsServiceError as e:
print(f"Error raised: {e}")
Obviously, this shall raise an error because variable_not_defined is, well, not defined.
However, for some strange reasons, the code executes correctly and prints
before
after
I have tried to call the function with an argument (retrieve_results(1234)) or adding an argument in the function (def retrieve_results(arg1) and retrieve_results()): both modifications will trigger an exception, so obviously the function is called.
Anyone has got a similar issue and knows what happens?
FYI: this is actually what my implementation looks like:
from azure.devops.exceptions import AzureDevOpsServiceError
import logging
def _retrieve_manual_results(connect: Connectivity, data: DataForPickle) -> None:
"""Retrieve the list of Test Results"""
print("G" + ggggggggggggggggggggggggggggggggggggg)
logger = connect.logger
data.run_in_progress = [165644]
if __name__ == "__main__":
p = ...
connect = ...
data = ...
if not "results" in p.options.skip:
try:
print("........B.........")
_retrieve_manual_results(connect, data)
print("........A.........")
except AzureDevOpsServiceError as e:
logging.error(f"E004: Error while retrieving Test Results: {e}")
logging.debug("More details below...", exc_info=True)
As highlighted by #gmds, it was a problem of cache.
Deleting the .pyc file didn't do much.
However, I have found a solution:
Renaming the function (e.g. adding _)
Running the program
Renaming back (i.e. removing _ in the previous example)
Now, the issue is solved.
If anyone knows what is going behind the scene I am very interested.

unittest - assertRaises return an error instead of passing

I've written a piece of code that uses a config file (in JSON-format)
def test_read_config_file(self):
self.assertRaises(ValueError, self.read_config_file('no_json.txt')
The original function looks like this:
def read_config_file(file_name)
config_data = None
try:
with open(file_name, 'r') as infile:
config_data = json.load(infile)
except ValueError as err:
LOGGER.error(str(err))
return config_data
When i run my testcase i get this:
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
no_json.txt just contains "Hi". Why am i getting 4 error here?
Thanks,
You are not using the unittest library correctly. When you write this:
def test_read_config_file(self):
self.assertRaises(ValueError, self.read_config_file('no_json.txt'))
# Btw. there was a missing closing `)`
the self.read_config_file() method is executed before the self.assertRaises. If it fails the self.assertRaises will never be called. Instead the exception bubbles up until something else catches it.
You want the self.assertRaises method to execute the self.read_config_file method. Because then and only then it can catch a potential ValueError. To do this you have two options:
Pass the method to test and the arguments separately:
self.assertRaises(ValueError, self.read_config_file, "no_json.txt")
Like this self.assertRaises will call the function you passed into it with the arguments specified. Then the exception occurs inside self.assertRaises where it can be caught and let the test succeed.
The second option is to use a context manager:
def test_read_config_file(self):
with self.assertRaises(ValueError):
self.read_config_file("no_json.txt")
Like this the exception will happen inside the with statement. In the cleanup step of the context manager the presence of such an exception can then again let the test succeed.
EDIT:
From your edit i can see that you already handle the ValueError in your self.read_config_file method. So any self.assertRaises approach will fail anyway. Either let the self.read_config_file raise the error or change your test.
The problem is that your function catches the ValueError exception that json.load raises, performs the logging, then simply goes on to return the value of config_data. Your test asserts that the function should raise an exception, but there is no code to ensure that the function does that.
The easiest way to fix this would be to modify the code by adding a raise statement to ensure that the ValueError is re-raised to be trapped by the assertRaises call:
def read_config_file(file_name)
config_data = None
try:
with open(file_name, 'r') as infile:
config_data = json.load(infile)
except ValueError as err:
LOGGER.error(str(err))
raise
return config_data

How to cover the except portion of a try-except in a python pytest unit test

I'm new to Python. I need to unit test the except part of a try-except statement in python. I'm using pytest. The problem is that I don't know how to force the try part to raise an exception. Here is my code:
try:
if master_bill_to is False:
master.update(
dbsession,
company_id=master.company_id,
)
except Exception as e:
dbsession.rollback()
raise Conflict(e.message)
The master.update method is called to make an update to the database. But how do I mock this code so that it somehow raises an exception in the try portion?
I'm trying to use monkeypatch with this code. The master object is an instance of the BillTo class so I'm thinking of putting that as the first parameter to monkeypatch.setattr.
def test_create_bill_to_fails_when_master_update_fails(dbsession, invoice_group1, company1,
monkeypatch):
def raise_flush_error():
raise FlushError
context = TestContext()
monkeypatch.setattr(BillTo, 'update', raise_flush_error)
with pytest.raises(FlushError):
create_bill_to(
context,
dbsession=dbsession,
invoice_group_id=invoice_group1.id,
company_id=company1.id,
)
But for some reason, the error is not raised.
Use mock library and side_effect to throw Exception during test case
Mock master and raise an exception in the update method.
Ok, I figured it out. I learned that you must pass parameters to the method called by monkeypatch. These parameters must match the signature of the method being replaced or mocked. Actually I also renamed the method with a prefix fake_ to denote the mocking. Here is what I did:
#staticmethod
def fake_update_flush_error(dbsession, company_id=None, address_id=None, proportion=None,
company_name=None, receiver_name=None, invoice_delivery_method=None,
invoice_delivery_text=None, master_bill_to=False):
raise FlushError
def test_create_bill_to_fails_when_master_update_fails(dbsession, invoice_group1, company1,
bill_to1, monkeypatch):
context = TestContext()
monkeypatch.setattr(BillTo, 'update', fake_update_flush_error)
with pytest.raises(Conflict):
create_bill_to(
context,
dbsession=dbsession,
invoice_group_id=invoice_group1.id,
company_id=company1.id,
address_id=None,
...
)
The BillTo.update method needed all those parameters.

Can't catch mocked exception because it doesn't inherit BaseException

I'm working on a project that involves connecting to a remote server, waiting for a response, and then performing actions based on that response. We catch a couple of different exceptions, and behave differently depending on which exception is caught. For example:
def myMethod(address, timeout=20):
try:
response = requests.head(address, timeout=timeout)
except requests.exceptions.Timeout:
# do something special
except requests.exceptions.ConnectionError:
# do something special
except requests.exceptions.HTTPError:
# do something special
else:
if response.status_code != requests.codes.ok:
# do something special
return successfulConnection.SUCCESS
To test this, we've written a test like the following
class TestMyMethod(unittest.TestCase):
def test_good_connection(self):
config = {
'head.return_value': type('MockResponse', (), {'status_code': requests.codes.ok}),
'codes.ok': requests.codes.ok
}
with mock.patch('path.to.my.package.requests', **config):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.SUCCESS
)
def test_bad_connection(self):
config = {
'head.side_effect': requests.exceptions.ConnectionError,
'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError
}
with mock.patch('path.to.my.package.requests', **config):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.FAILURE
)
If I run the function directly, everything happens as expected. I even tested by adding raise requests.exceptions.ConnectionError to the try clause of the function. But when I run my unit tests, I get
ERROR: test_bad_connection (test.test_file.TestMyMethod)
----------------------------------------------------------------
Traceback (most recent call last):
File "path/to/sourcefile", line ###, in myMethod
respone = requests.head(address, timeout=timeout)
File "path/to/unittest/mock", line 846, in __call__
return _mock_self.mock_call(*args, **kwargs)
File "path/to/unittest/mock", line 901, in _mock_call
raise effect
my.package.requests.exceptions.ConnectionError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "Path/to/my/test", line ##, in test_bad_connection
mypackage.myMethod('some_address',
File "Path/to/package", line ##, in myMethod
except requests.exceptions.ConnectionError:
TypeError: catching classes that do not inherit from BaseException is not allowed
I tried to change the exception I was patching in to BaseException and I got a more or less identical error.
I've read https://stackoverflow.com/a/18163759/3076272 already, so I think it must be a bad __del__ hook somewhere, but I'm not sure where to look for it or what I can even do in the mean time. I'm also relatively new to unittest.mock.patch() so it's very possible that I'm doing something wrong there as well.
This is a Fusion360 add-in so it is using Fusion 360's packaged version of Python 3.3 - as far as I know it's a vanilla version (i.e. they don't roll their own) but I'm not positive of that.
I could reproduce the error with a minimal example:
foo.py:
class MyError(Exception):
pass
class A:
def inner(self):
err = MyError("FOO")
print(type(err))
raise err
def outer(self):
try:
self.inner()
except MyError as err:
print ("catched ", err)
return "OK"
Test without mocking :
class FooTest(unittest.TestCase):
def test_inner(self):
a = foo.A()
self.assertRaises(foo.MyError, a.inner)
def test_outer(self):
a = foo.A()
self.assertEquals("OK", a.outer())
Ok, all is fine, both test pass
The problem comes with the mocks. As soon as the class MyError is mocked, the expect clause cannot catch anything and I get same error as the example from the question :
class FooTest(unittest.TestCase):
def test_inner(self):
a = foo.A()
self.assertRaises(foo.MyError, a.inner)
def test_outer(self):
with unittest.mock.patch('foo.MyError'):
a = exc2.A()
self.assertEquals("OK", a.outer())
Immediately gives :
ERROR: test_outer (__main__.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "...\foo.py", line 11, in outer
self.inner()
File "...\foo.py", line 8, in inner
raise err
TypeError: exceptions must derive from BaseException
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<pyshell#78>", line 8, in test_outer
File "...\foo.py", line 12, in outer
except MyError as err:
TypeError: catching classes that do not inherit from BaseException is not allowed
Here I get a first TypeErrorthat you did not have, because I am raising a mock while you forced a true exception with 'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError in config. But the problem remains that the except clause tries to catch a mock.
TL/DR: as you mock the full requests package, the except requests.exceptions.ConnectionError clause tries to catch a mock. As the mock is not really a BaseException, it causes the error.
The only solution I can imagine is not to mock the full requests but only the parts that are not exceptions. I must admit I could not find how to say to mock mock everything except this but in your example, you only need to patch requests.head. So I think that this should work :
def test_bad_connection(self):
with mock.patch('path.to.my.package.requests.head',
side_effect=requests.exceptions.ConnectionError):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.FAILURE
)
That is : only patch the head method with the exception as side effect.
I just ran into the same issue while trying to mock sqlite3 (and found this post while looking for solutions).
What Serge said is correct:
TL/DR: as you mock the full requests package, the except requests.exceptions.ConnectionError clause tries to catch a mock. As the mock is not really a BaseException, it causes the error.
The only solution I can imagine is not to mock the full requests but only the parts that are not exceptions. I must admit I could not find how to say to mock mock everything except this
My solution was to mock the entire module, then set the mock attribute for the exception to be equal to the exception in the real class, effectively "un-mocking" the exception. For example, in my case:
#mock.patch(MyClass.sqlite3)
def test_connect_fail(self, mock_sqlite3):
mock_sqlite3.connect.side_effect = sqlite3.OperationalError()
mock_sqlite3.OperationalError = sqlite3.OperationalError
self.assertRaises(sqlite3.OperationalError, MyClass, self.db_filename)
For requests, you could assign exceptions individually like this:
mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError
or do it for all of the requests exceptions like this:
mock_requests.exceptions = requests.exceptions
I don't know if this is the "right" way to do it, but so far it seems to work for me without any issue.
For those of us who need to mock an exception and can't do that by simply patching head, here is an easy solution that replaces the target exception with an empty one:
Say we have a generic unit to test with an exception we have to have mocked:
# app/foo_file.py
def test_me():
try:
foo()
return "No foo error happened"
except CustomError: # <-- Mock me!
return "The foo error was caught"
We want to mock CustomError but because it is an exception we run into trouble if we try to patch it like everything else. Normally, a call to patch replaces the target with a MagicMock but that won't work here. Mocks are nifty, but they do not behave like exceptions do. Rather than patching with a mock, let's give it a stub exception instead. We'll do that in our test file.
# app/test_foo_file.py
from mock import patch
# A do-nothing exception we are going to replace CustomError with
class StubException(Exception):
pass
# Now apply it to our test
#patch('app.foo_file.foo')
#patch('app.foo_file.CustomError', new_callable=lambda: StubException)
def test_foo(stub_exception, mock_foo):
mock_foo.side_effect = stub_exception("Stub") # Raise our stub to be caught by CustomError
assert test_me() == "The error was caught"
# Success!
So what's with the lambda? The new_callable param calls whatever we give it and replaces the target with the return of that call. If we pass our StubException class straight, it will call the class's constructor and patch our target object with an exception instance rather than a class which isn't what we want. By wrapping it with lambda, it returns our class as we intend.
Once our patching is done, the stub_exception object (which is literally our StubException class) can be raised and caught as if it were the CustomError. Neat!
I faced a similar issue while trying to mock the sh package. While sh is very useful, the fact that all methods and exceptions are defined dynamically make it more difficult to mock them. So following the recommendation of the documentation:
import unittest
from unittest.mock import Mock, patch
class MockSh(Mock):
# error codes are defined dynamically in sh
class ErrorReturnCode_32(BaseException):
pass
# could be any sh command
def mount(self, *args):
raise self.ErrorReturnCode_32
class MyTestCase(unittest.TestCase):
mock_sh = MockSh()
#patch('core.mount.sh', new=mock_sh)
def test_mount(self):
...
I just ran into the same problem when mocking struct.
I get the error:
TypeError: catching classes that do not inherit from BaseException is not allowed
When trying to catch a struct.error raised from struct.unpack.
I found that the simplest way to get around this in my tests was to simply set the value of the error attribute in my mock to be Exception. For example
The method I want to test has this basic pattern:
def some_meth(self):
try:
struct.unpack(fmt, data)
except struct.error:
return False
return True
The test has this basic pattern.
#mock.patch('my_module.struct')
def test_some_meth(self, struct_mock):
'''Explain how some_func should work.'''
struct_mock.error = Exception
self.my_object.some_meth()
struct_mock.unpack.assert_called()
struct_mock.unpack.side_effect = struct_mock.error
self.assertFalse(self.my_object.some_meth()
This is similar to the approach taken by #BillB, but it is certainly simpler as I don't need to add imports to my tests and still get the same behavior. To me it would seem this is the logical conclusion to the general thread of reasoning in the answers here.
Use patch.object to partially mock a class.
My use case:
import unittest
from unittest import mock
import requests
def test_my_function(self):
response = mock.MagicMock()
response.raise_for_status.side_effect = requests.HTTPError
with mock.patch.object(requests, 'get', return_value=response):
my_function()

Categories

Resources