Given a module
some_module.py
_foo = Foo(config)
def do_foo():
return _foo.foo()
Foo.__init__ raises an exception, so I am not able to write a test since the exception is already raised at import time in some_module.py.
I want to patch the module to replace _foo with an object of another type, say TestFoo. I tried using mock.patch, but this does not work because the exception is already raised before the patching is applied.
I am coming from java where I would have injected through constructor while testing, which is easy. What are my options in Python?
Related
I am using pytest-mock, but the exception being thrown is from the mock.patch code, I have verified that the same errot occurs if I use the #mock.patch decorator syntax.
MRE (ensure you have pytest and pytest-mock installed, no need to import anything):
def test_image(mocker):
mocker.patch("PIL.Image.save")
Now run pytest on this module.
Error:
E AttributeError: <module 'PIL.Image' from 'c:\\users\\...\\site-packages\\PIL\\Image.py'> does not have the attribute 'save'
I can see clearly that Image.py does contain a function called save, but are functions not considered attributes? I've never heard that word used for the contents of a module.
save is an instance method of PIL.Image.Image class and not PIL.Image module.
You should implement the patch as:
def test_image(mocker):
mocker.patch("PIL.Image.Image.save")
If you need to make assertions that the save method is invoked on the Image instance, you need a name that is bound to the mock.
You can implement that by mocking the Image class and binding a Mock instance to its save method . For example,
def test_image(mocker):
# prepare
klass = mocker.patch("PIL.Image.Image")
instance = klass.return_value
instance.save = mocker.Mock()
# act
# Do operation that invokes save method on `Image` instance
# test
instance.save.assert_called()
I'm doing some self-learning on the new python dataclasses.
One of the parameters that can be passed to the dataclass decorator is frozen=True, to make the object immutable.
The documentation (and experience) indicates that a:
dataclasses.FrozenInstanceError
exception will be raised.
When unit testing though (with pytest) the following test passes:
def test_change_page_url_values_raises_error(self, PAGE_URL):
page_url = PageURL(PAGE_URL)
with pytest.raises(AttributeError) as error:
page_url.value = PAGE_URL
where PageURL is a dataclass with the frozen=True parameter.
Any ideas why why pytest indicates that this action (assigning a value to page_url.value) raises an Attribute Error? Does FrozenInstanceError inherit from AttributeError?
Note: If I change the unit test to test for a different exception (ie. TypeError), the test fails as expected.
This is a subclass, which you can verify easily with built-in function issubclass:
>>> issubclass(FrozenInstanceError, AttributeError)
True
If you want an exact type match in the tests, which I would consider best practice, then you can use an exception instance instead of an exception class. As an added bonus this also allows you to make an assertion on the exception context (i.e. which field has triggered the exception).
with pytest.raises(FrozenInstanceError("cannot assign to field 'value'")):
page_url.value = PAGE_URL
This usage of pytest.raises requires installing my plugin pytest-raisin.
I have created my custom exceptions as such within errors.py
mapper = {
'E101':
'There is no data at all for these constraints',
'E102':
'There is no data for these constraints in this market, try changing market',
'E103':
'There is no data for these constraints during these dates, try changing dates',
}
class DataException(Exception):
def __init__(self, code):
super().__init__()
self.msg = mapper[code]
def __str__(self):
return self.msg
Another function somewhere else in the code raises different instances of DataException if there is not enough data in a pandas dataframe. I want to use unittest to ensure that it returns the appropriate exception with its corresponding message.
Using a simple example, why does this not work:
from .. import DataException
def foobar():
raise DataException('E101')
import unittest
with unittest.TestCase.assertRaises(DataException):
foobar()
As suggested here: Python assertRaises on user-defined exceptions
I get this error:
TypeError: assertRaises() missing 1 required positional argument: 'expected_exception'
Or alternatively:
def foobar():
raise DataException('E101')
import unittest
unittest.TestCase.assertRaises(DataException, foobar)
results in:
TypeError: assertRaises() arg 1 must be an exception type or tuple of exception types
Why is it not recognizing DataException as an Exception? Why does the linked stackoverflow question answer work without supplying a second argument to assertRaises?
You are trying to use methods of the TestCase class without creating an instance; those methods are not designed to be used in that manner.
unittest.TestCase.assertRaises is an unbound method. You'd use it in a test method on a TestCase class you define:
class DemoTestCase(unittest.TestCase):
def test_foobar(self):
with self.assertRaises(DataException):
foobar()
The error is raised because unbound methods do not get self passed in. Because unittest.TestCase.assertRaises expects both self and a second argument named expected_exception you get an exception as DataException is passed in as the value for self.
You do now have to use a test runner to manage your test cases; add
if __name__ == '__main__':
unittest.main()
at the bottom and run your file as a script. Your test cases are then auto-discovered and executed.
It is technically possible to use the assertions outside such an environment, see Is there a way to use Python unit test assertions outside of a TestCase?, but I recommend you stick to creating test cases instead.
To further verify the codes and message on the raised exception, assign the value returned when entering the context to a new name with with ... as <target>:; the context manager object captures the raised exception so you can make assertions about it:
with self.assertRaises(DataException) as context:
foobar()
self.assertEqual(context.exception.code, 'E101')
self.assertEqual(
context.exception.msg,
'There is no data at all for these constraints')
See the TestCase.assertRaises() documentation.
Last but not least, consider using subclasses of DataException rather than use separate error codes. That way your API users can just catch one of those subclasses to handle a specific error code, rather than having to do additional tests for the code and re-raise if a specific code should not have been handled there.
I'm using python mock library (python 2.7, mock==1.0.1) and when mocking out certain parts of code that I'm testing mock is swallowing exceptions for some reason.
Below is an example:
#test.py
from django import test
from something import main_func
class TestCase(test.TestCase):
#mock.patch('something.somewhere')
def test_something(mock_somewhere):
main_func()
#something.py
def somewhere(param):
print param
def main_func():
somewhere(None.missing_something)
So AttributeError should be raised right? This test is passing on my machine, in reality the code is more complicated, a Django Model is supposed to be saved and existing. The test is failing because the model doesn't exist.
If I insert an import ipdb; ipdb.set_trace() just before somewhere(None.missing_method) then I can see the AttributeException is raised but it doesn't show up in the test.
Any ideas?
I think you need to use a 'spec' eg autospec=True
http://www.voidspace.org.uk/python/mock/patch.html
This will then ensure that the generated mock will raise attribute errors if you try to access an attribute that didn't exist on the original object, otherwise mock will just return a new mock for any attr access
eg
from something import main_func
class TestCase(test.TestCase):
#mock.patch('something.somewhere', autospec=True)
def test_something(mock_somewhere):
main_func()
I have a module A.py, that holds some constants, and a class. The class is not ready yet, so meny of it's functions implementations are NOT IMPLEMENTED YET - that is, I want if anyone calls this function, python to fail with syntactic error.
However, now I am doing from A import constants and python is failing with the error within the function within the class.
How can I resolve this?
Rather than leaving in the syntax errors, explicitly raise NotImplementedError from your unfinished function. Or move the constants to a separate module.
Add pass to your methods:
class SomeClass(object):
def some_method(self):
pass
def method(self):
pass
This way, you will not have issues importing the class.
The other option is to raise NotImplementedError.
As importing will evaluate the module, you need to have error free code in your file.