Python: Mock side_effect on object attribute - python

Is it possible to have a side_effect on a property? If I look at the Mock documentation it seems it's only possible on object methods.
I am trying to test the following:
def get_object(self):
try:
return self.request.user.shop
except Shop.DoesNotExist:
return None
I want Shop to raise a DoesNotExist exception.
Guess maybe I wasn't clear enough but I am talking about the voidspace mock library.
http://www.voidspace.org.uk/python/mock/index.html

It's worth noting that there is now the PropertyMock class:
>>> m = MagicMock()
>>> p = PropertyMock(side_effect=ValueError)
>>> type(m).foo = p
>>> m.foo
Traceback (most recent call last):
....
ValueError
That example was taken from the official site.

You can also try to patch the related field using a PropertyMock as new_callable argument.
Example:
from unittest import TestCase
import mock
from django import models
from django.core.exceptions import ObjectDoesNotExist
class Foo(models.Model):
# ...
#property
def has_pending_related(self):
try:
return self.related_field.is_pending
except ObjectDoesNotExist:
return False
class FooTestCase(TestCase):
# ...
#mock.patch.object(Foo, 'related_field', new_callable=mock.PropertyMock)
def test_pending_related(self, related_field):
related_field.side_effect = ObjectDoesNotExist
foo = Foo()
self.assertFalse(foo.has_pending_related)

Yes, you can use a property for it:
In [1]: class ShopDoesNotExist(Exception):
...: pass
...:
In [2]: class User(object):
...: #property
...: def shop(self):
...: raise ShopDoesNotExist
...:
...:
In [3]: u = User()
In [4]: u.shop
---------------------------------------------------------------------------
ShopDoesNotExist Traceback (most recent call last)

The author made a blog post about this problem. I went for the first solution which looks like this:
class UserMock(Mock):
#property
def shop(self):
raise Shop.DoesNotExist()
http://www.voidspace.org.uk/python/weblog/arch_d7_2010_11_20.shtml#e1196

As an aside, if you're trying to test for AttributeError as an side_effect you can use spec parameter and set it to empty list (i.e. []). Say you're trying to create a Mock object that throws an AttributeError when accessing attribute foo:
from unittest.mock import Mock
m = Mock(spec = [])
m.foo
# AttributeError: Mock object has no attribute 'foo'
See Documentation:
spec: This can be either a list of strings or an existing object (a class or instance) that acts as the specification for the mock object. If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an AttributeError.

Related

Mocking class attributes and methods with Python and Pytest [duplicate]

How do you mock a readonly property with mock?
I tried:
setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
but the issue is that it then applies to all instances of the class... which breaks my tests.
Do you have any other idea? I don't want to mock the full object, only this specific property.
I think the better way is to mock the property as PropertyMock, rather than to mock the __get__ method directly.
It is stated in the documentation, search for unittest.mock.PropertyMock:
A mock intended to be used as a property, or other descriptor, on a class. PropertyMock provides __get__ and __set__ methods so you can specify a return value when it is fetched.
Here is how:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
def test(unittest.TestCase):
with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
Actually, the answer was (as usual) in the documentation, it's just that I was applying the patch to the instance instead of the class when I followed their example.
Here is how to do it:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
In the test suite:
def test():
# Make sure you patch on MyClass, not on a MyClass instance, otherwise
# you'll get an AttributeError, because mock is using settattr and
# last_transaction is a readonly property so there's no setter.
with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
myclass = MyClass()
print myclass.last_transaction
If the object whose property you want to override is a mock object, you don't have to use patch.
Instead, can create a PropertyMock and then override the property on the type of the mock. For example, to override mock_rows.pages property to return (mock_page, mock_page,):
mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages
In case you are using pytest along with pytest-mock, you can simplify your code and also avoid using the context manger, i.e., the with statement as follows:
def test_name(mocker): # mocker is a fixture included in pytest-mock
mocked_property = mocker.patch(
'MyClass.property_to_be_mocked',
new_callable=mocker.PropertyMock,
return_value='any desired value'
)
o = MyClass()
print(o.property_to_be_mocked) # this will print: any desired value
mocked_property.assert_called_once_with()
Probably a matter of style but in case you prefer decorators in tests, #jamescastlefield's answer could be changed to something like this:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
class Test(unittest.TestCase):
#mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
def test(self, mock_last_transaction):
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
If you need your mocked #property to rely on the original __get__, you can create your custom MockProperty
class PropertyMock(mock.Mock):
def __get__(self, obj, obj_type=None):
return self(obj, obj_type)
Usage:
class A:
#property
def f(self):
return 123
original_get = A.f.__get__
def new_get(self, obj_type=None):
return f'mocked result: {original_get(self, obj_type)}'
with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
mock_foo.side_effect = new_get
print(A().f) # mocked result: 123
print(mock_foo.call_count) # 1
If you don't want to test whether or not the mocked property was accessed you can simply patch it with the expected return_value.
with mock.patch(MyClass, 'last_transaction', Transaction()):
...
I was directed to this question because I wanted to mock the Python version in a test. Not sure whether this is quite relevant to this question, but sys.version is obviously read-only (... though technically an "attribute" rather than a "property", I suppose).
So, after perusing this place and trying some stupidly complicated stuff I realised the answer was simplicity itself:
with mock.patch('sys.version', version_tried):
if version_tried == '2.5.2':
with pytest.raises(SystemExit):
import core.__main__
_, err = capsys.readouterr()
assert 'FATAL' in err and 'too old' in err
... might help someone.

factoryboy not overriding django model properties

I'm trying to override a custom Django model property via factory_boy for testing purposes. But it seems like it is simply taking the default behavior of the model. is factory boy not able to change the default behaviour of custom attributes?
Here is a basic test I wrote:
models.py:
class Session(models.Model):
name = models.CharField(max_length=100)
#property
def foo(self):
return method_not_callable_in_testing()
def method_not_callable_in_testing():
return 42
SessionFactory.py:
class SessionFactory(factory.django.DjangoModelFactory):
class Meta:
model = Session
name = "session"
foo = 1337
tests.py:
class TestSession(TestCase):
def test_custom_attribute_overwritten_by_factoryboy(self):
session = SessionFactory.create()
self.assertEquals(session.foo, 1337)
When running the tests I get the following error:
F
======================================================================
FAIL: test_custom_attribute_overwritten_by_factoryboy (bar.tests.TestSession)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/sh4ke/repos/foo/bar/tests.py", line 10, in test_custom_attribute_overwritten_by_factoryboy
self.assertEquals(session.foo, 1337)
AssertionError: 42 != 1337
tl;dr
factory_boy can change "custom attributes", (which I assume you mean ones that are not django Fields), but it can't change read-only attributes, which is what you are trying to do.
More info
Your problem is that you are trying to use the factory to set an attribute for a read-only property. Roughly, the combination of factory_boy and django means that the call to SessionFactory() is doing:
session = Session()
session.foo = 1337
But I wouldn't expect this to work because unless you declare the #foo.setter method #property assumes you wanted a read-only property, and so raises attribute error when you attempt to set it.
The weird bit is why you don't see that error, and it turns out that django quietly suppresses it! It sees that your model has the foo attribute by doing getattr(session, 'foo'), but it has wrapped the whole thing including the setattr inside a try: except AttributeError:, so while it detects and raises an attempt to set an attribute that doesn't exist at all, it also catches and quietly ignores the failed case of an attempt to set a read-only property.
I suspect this is a bug... so will probably try and raise it with the django community.

How do you catch this exception?

This code is in django/db/models/fields.py It creates/defines an exception?
class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescriptorMethods)):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# a single "remote" value, on the class that defines the related field.
# In the example "choice.poll", the poll attribute is a
# ReverseSingleRelatedObjectDescriptor instance.
def __init__(self, field_with_rel):
self.field = field_with_rel
self.cache_name = self.field.get_cache_name()
#cached_property
def RelatedObjectDoesNotExist(self):
# The exception can't be created at initialization time since the
# related model might not be resolved yet; `rel.to` might still be
# a string model reference.
return type(
str('RelatedObjectDoesNotExist'),
(self.field.rel.to.DoesNotExist, AttributeError),
{}
)
This is in django/db/models/fields/related.py it raises the said exception above:
def __get__(self, instance, instance_type=None):
if instance is None:
return self
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
val = self.field.get_local_related_value(instance)
if None in val:
rel_obj = None
else:
params = dict(
(rh_field.attname, getattr(instance, lh_field.attname))
for lh_field, rh_field in self.field.related_fields)
qs = self.get_queryset(instance=instance)
extra_filter = self.field.get_extra_descriptor_filter(instance)
if isinstance(extra_filter, dict):
params.update(extra_filter)
qs = qs.filter(**params)
else:
qs = qs.filter(extra_filter, **params)
# Assuming the database enforces foreign keys, this won't fail.
rel_obj = qs.get()
if not self.field.rel.multiple:
setattr(rel_obj, self.field.related.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
if rel_obj is None and not self.field.null:
raise self.RelatedObjectDoesNotExist(
"%s has no %s." % (self.field.model.__name__, self.field.name)
)
else:
return rel_obj
The problem is that this code:
try:
val = getattr(obj, attr_name)
except related.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist:
val = None # Does not catch the thrown exception
except Exception as foo:
print type(foo) # Catches here, not above
won't catch that exception
>>>print type(foo)
<class 'django.db.models.fields.related.RelatedObjectDoesNotExist'>
>>>isinstance(foo, related.FieldDoesNotExist)
False
and
except related.RelatedObjectDoesNotExist:
Raises an AttributeError: 'module' object has no attribute 'RelatedObjectDoesNotExist'
>>>isinstance(foo, related.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist)
Traceback (most recent call last):
File "<string>", line 1, in <fragment>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
which is probably why.
If your related model is called Foo you can just do:
except Foo.DoesNotExist:
Django is amazing when it's not terrifying. RelatedObjectDoesNotExist is a property that returns a type that is figured out dynamically at runtime. That type uses self.field.rel.to.DoesNotExist as a base class.
According to Django documentation:
DoesNotExist
exception Model.DoesNotExist
This exception is raised by the ORM when an expected object is not
found. For example, QuerySet.get() will raise it when no object
is found for the given lookups.
Django provides a DoesNotExist exception as an attribute of
each model class to identify the class of object that could not be
found, allowing you to catch exceptions for a particular model class.
The exception is a subclass of django.core.exceptions.ObjectDoesNotExist.
This is the magic that makes that happen. Once the model has been built up, self.field.rel.to.DoesNotExist is the does-not-exist exception for that model.
If you don't want to import the related model class, you can:
except MyModel.related_field.RelatedObjectDoesNotExist:
or
except my_model_instance._meta.model.related_field.RelatedObjectDoesNotExist:
where related_field is the field name.
Let's say we have the following models:
class MainModel(Model):
pass
class RelatedModel(Model):
main = OneToOneField(MainModel, null=True, related_name="related")
You can get a RelatedObjectDoesNotExist exception with MainModel().related.
You have three options for catching this exception, which you can find by looking at .__class__.__mro__ of the exception:
MainModel.related.RelatedObjectDoesNotExist
RelatedModel.DoesNotExist
django.core.exceptions.ObjectDoesNotExist
MainModel.related.RelatedObjectDoesNotExist
RelatedObjectDoesNotExist is what the question is looking for, but is specific to a nullable OneToOneField:
try:
# Your code here
except MainModel.related.RelatedObjectDoesNotExist:
# Handle exception
RelatedModel.DoesNotExist
Model.DoesNotExist is the parent class of RelatedObjectDoesNotExist. To catch it requires you to be able to import the model in question, but is a more generically useful code pattern.
try:
# Your code here
except OtherModel.DoesNotExist:
# Handle exception
django.core.exceptions.ObjectDoesNotExist
ObjectDoesNotExist is the parent class of Model.DoesNotExist. This will catch this exception for any model, which is helpful if you don't know what model will raise the exception:
from django.core.exceptions import ObjectDoesNotExist
try:
# Your code here
except ObjectDoesNotExist:
# Handle exception
The RelatedObjectDoesNotExist exception is created dynamically at runtime. Here is the relevant code snippet for the ForwardManyToOneDescriptor and ReverseOneToOneDescriptor descriptors:
#cached_property
def RelatedObjectDoesNotExist(self):
# The exception can't be created at initialization time since the
# related model might not be resolved yet; `self.field.model` might
# still be a string model reference.
return type(
'RelatedObjectDoesNotExist',
(self.field.remote_field.model.DoesNotExist, AttributeError),
{}
)
So the exception inherits from <model name>.DoesNotExist and AttributeError. In fact, the complete MRO for this exception type is:
[<class 'django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist'>,
<class '<model module path>.DoesNotExist'>,
<class 'django.core.exceptions.ObjectDoesNotExist'>,
<class 'AttributeError'>,
<class 'Exception'>,
<class 'BaseException'>,
<class 'object'>]
The basic takeaway is you can catch <model name>.DoesNotExist, ObjectDoesNotExist (import from django.core.exceptions) or AttributeError, whatever makes the most sense in your context.
Little bit late but helpful for others.
2 ways to handle this.
1st :
When we need to catch exception
>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>> p2.restaurant
>>> except ObjectDoesNotExist:
>>> print("There is no restaurant here.")
There is no restaurant here.
2nd:
When don't want to handle exception
>>> hasattr(p2, 'restaurant')
False
tdelaney's answer is great for regular code paths, but if you need to know how to catch this exception in tests:
from django.core.exceptions import ObjectDoesNotExist
...
def testCompanyRequired(self):
with self.assertRaises(ObjectDoesNotExist):
employee = Employee.objects.create()

How to mock a readonly property with mock?

How do you mock a readonly property with mock?
I tried:
setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
but the issue is that it then applies to all instances of the class... which breaks my tests.
Do you have any other idea? I don't want to mock the full object, only this specific property.
I think the better way is to mock the property as PropertyMock, rather than to mock the __get__ method directly.
It is stated in the documentation, search for unittest.mock.PropertyMock:
A mock intended to be used as a property, or other descriptor, on a class. PropertyMock provides __get__ and __set__ methods so you can specify a return value when it is fetched.
Here is how:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
def test(unittest.TestCase):
with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
Actually, the answer was (as usual) in the documentation, it's just that I was applying the patch to the instance instead of the class when I followed their example.
Here is how to do it:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
In the test suite:
def test():
# Make sure you patch on MyClass, not on a MyClass instance, otherwise
# you'll get an AttributeError, because mock is using settattr and
# last_transaction is a readonly property so there's no setter.
with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
myclass = MyClass()
print myclass.last_transaction
If the object whose property you want to override is a mock object, you don't have to use patch.
Instead, can create a PropertyMock and then override the property on the type of the mock. For example, to override mock_rows.pages property to return (mock_page, mock_page,):
mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages
In case you are using pytest along with pytest-mock, you can simplify your code and also avoid using the context manger, i.e., the with statement as follows:
def test_name(mocker): # mocker is a fixture included in pytest-mock
mocked_property = mocker.patch(
'MyClass.property_to_be_mocked',
new_callable=mocker.PropertyMock,
return_value='any desired value'
)
o = MyClass()
print(o.property_to_be_mocked) # this will print: any desired value
mocked_property.assert_called_once_with()
Probably a matter of style but in case you prefer decorators in tests, #jamescastlefield's answer could be changed to something like this:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
class Test(unittest.TestCase):
#mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
def test(self, mock_last_transaction):
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
If you need your mocked #property to rely on the original __get__, you can create your custom MockProperty
class PropertyMock(mock.Mock):
def __get__(self, obj, obj_type=None):
return self(obj, obj_type)
Usage:
class A:
#property
def f(self):
return 123
original_get = A.f.__get__
def new_get(self, obj_type=None):
return f'mocked result: {original_get(self, obj_type)}'
with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
mock_foo.side_effect = new_get
print(A().f) # mocked result: 123
print(mock_foo.call_count) # 1
If you don't want to test whether or not the mocked property was accessed you can simply patch it with the expected return_value.
with mock.patch(MyClass, 'last_transaction', Transaction()):
...
I was directed to this question because I wanted to mock the Python version in a test. Not sure whether this is quite relevant to this question, but sys.version is obviously read-only (... though technically an "attribute" rather than a "property", I suppose).
So, after perusing this place and trying some stupidly complicated stuff I realised the answer was simplicity itself:
with mock.patch('sys.version', version_tried):
if version_tried == '2.5.2':
with pytest.raises(SystemExit):
import core.__main__
_, err = capsys.readouterr()
assert 'FATAL' in err and 'too old' in err
... might help someone.

Mocking a class method that is used via an instance

I'm trying to patch a class method using mock as described in the documentation. The Mock object itself works fine, but its methods don't: For example, their attributes like call_count aren't updated, even though the method_calls attribute of the class Mock object is. More importantly, their return_value attribute is ignored:
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self):
l = Lib()
return l.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock.method.return_value = "mock"
u = User()
print(u.run())
>>>
mock
<MagicMock name='Lib().method()' id='39868624'>
What am I doing wrong here?
EDIT: Passing a class Mock via the constructor doesn't work either, so this is not really related to the patch function.
I have found my error: In order to configure the methods of my mock's instances, I have to use mock().method instead of mock.method.
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self):
l = Lib()
return l.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock().method.return_value = "mock"
u = User()
print(u.run())
from mock import *
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self, m):
return m.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock.method.return_value = "mock"
print User().run(mock)
I mock classmethods like this:
def raiser(*args, **kwargs):
raise forms.ValidationError('foo')
with mock.patch.object(mylib.Commands, 'my_class_method', classmethod(raiser)):
response=self.admin_client.get(url, data=dict(term='+1000'))

Categories