How can I mock a method of a Django model manager? - python

Here is a little class (in myapp/getters.py):
from django.contrib.auth.models import User
class UserGetter:
def get_user(self):
return User.objects.get(username='username')
I would like to mock out the call to User.objects.get, return a MagicMock, and test that the method returns what I injected. In myapp/tests/tests_getters.py:
from unittest import TestCase
from django.contrib.auth.models import User, UserManager
from mock import patch, create_autospec
from myapp.getters import UserGetter
class MockTestCase(TestCase):
#patch('myapp.getters.User', autospec=True)
def test(self, user_class):
user = create_autospec(User)
objects = create_autospec(UserManager)
objects.get.return_value = user
user_class.objects.return_value = objects
self.assertEquals(user, UserGetter().get_user())
But when I run this test (with python manage.py test myapp.tests.tests_getters) I get
AssertionError:
<MagicMock name='User.objects().get()' spec='User' id='4354507472'> !=
<MagicMock name='User.objects.get()' id='4360679248'>
Why do I not get back the mock I injected? How can I write this test correctly?

I think this is your problem:
user_class.objects.return_value = objects
You instruct the mock to have a function "objects" that returns the objects on the right side.
But your code never calls any objects() function. It accesses the User.objects property, User is a Mock here, so User returns a new Mock on property access.

Related

Check the instance of a Mocked method called once

My function looks for a Actor object in the database and calls its do_something() method with a passed argument.
from my_app.models import Actor
def my_function(id, stuff):
actor = Actor.objects.get(id=id)
return actor.do_something(stuff)
I want my unit test to check two things :
1. my_function finds the Actor I want.
2. my_function calls the actor's do_something method as expected.
from unittest import mock
from django.test import TestCase
from my_app.models import Actor
from my_app.views import my_function
class ViewsTestCase(TestCase):
#classmethod
def setUpTestData(cls):
self.actor = Actor.objects.create(id=42, name='John')
def test_my_function(self):
with mock.patch.object(Actor, 'do_something') as mock_do:
my_function(id=42, stuff='a-short-string')
mock_do.assert_called_once_with('a-short-string')
This works to make sure my_function called do_something like I wanted but I don't know how to be sure it found the Actor I asked him to find. This test would pass even if my_function found the wrong actor. Is there any way to check that ?
First of all, I am not sure if this is the best practice of asserting on self param of a mocked method.
By adding autospec=True to your mock statement, the self param itself will become accessible in mocked object call_args. To be more clear your test case would be something like this:
from unittest import mock
from django.test import TestCase
from my_app.models import Actor
from my_app.views import my_function
class ViewsTestCase(TestCase):
#classmethod
def setUpTestData(cls):
self.actor = Actor.objects.create(id=42, name='John')
def test_my_function(self):
with mock.patch.object(Actor, 'do_something', autospec=True) as mock_do:
^^^^^^^^^^^^^
my_function(id=42, stuff='a-short-string')
mock_do.assert_called_once_with(self.actor, 'a-short-string')

In Django Factory Boy, is it possible to mute a specific receiver (not all signals)?

In our Django project, we have a receiver function for post_save signals sent by the User model:
#receiver(post_save, sender=User)
def update_intercom_attributes(sender, instance, **kwargs):
# if the user is not yet in the app, do not attempt to setup/update their Intercom profile.
if instance.using_app:
intercom.update_intercom_attributes(instance)
This receiver calls an external API, and we'd like to disable it when generating test fixtures with factory_boy. As far as I can tell from https://factoryboy.readthedocs.io/en/latest/orms.html#disabling-signals, however, all one can do is mute all post_save signals, not a specific receiver.
At the moment, the way we are going about this is by defining an IntercomMixin which every test case inherits from (in the first position in the inheritance chain):
from unittest.mock import patch
class IntercomMixin:
#classmethod
def setUpClass(cls):
cls.patcher = patch('lucy_web.lib.intercom.update_intercom_attributes')
cls.intercomMock = cls.patcher.start()
super().setUpClass()
#classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.patcher.stop()
However, it is cumbersome and repetitive to add this mixin to every test case, and ideally, we'd like to build this patching functionality into the test factories themselves.
Is there any way to do this in Factory Boy? (I had a look at the source code (https://github.com/FactoryBoy/factory_boy/blob/2d735767b7f3e1f9adfc3f14c28eeef7acbf6e5a/factory/django.py#L256) and it seems like the __enter__ method is setting signal.receivers = []; perhaps this could be modified so that it accepts a receiver function and pops it out of the the signal.receivers list)?
For anyone looking for just this thing and finding themselves on this question you can find the solution here: https://stackoverflow.com/a/26490827/1108593
Basically... call #factory.django.mute_signals(post_save) on the test method itself; or in my case the setUpTestData method.
Test:
# test_models.py
from django.test import TestCase
from django.db.models.signals import post_save
from .factories import ProfileFactory
import factory
class ProfileTest(TestCase):
#classmethod
#factory.django.mute_signals(post_save)
def setUpTestData(cls):
ProfileFactory(id=1) # This won't trigger user creation.
...
Profile Factory:
#factories.py
import factory
from factory.django import DjangoModelFactory
from profiles.models import Profile
from authentication.tests.factories import UserFactory
class ProfileFactory(DjangoModelFactory):
class Meta:
model = Profile
user = factory.SubFactory(UserFactory)
This allows your factories to keep working as expected and the tests to manipulate them as needed to test what they need.
In case you want to mute all signals of a type, you can configure that on your factory directly. For example:
from django.db.models.signals import post_save
#factory.django.mute_signals(post_save)
class UserFactory(DjangoModelFactory):
...

mocking a function within a class method

I want to mock a function which is called within a class method while testing the class method in a Django project. Consider the following structure:
app/utils.py
def func():
...
return resp # outcome is a HTTPResponse object
app/models.py
from app.utils import func
class MyModel(models.Model):
# fields
def call_func(self):
...
func()
...
app/tests/test_my_model.py
from django.test import TestCase
import mock
from app.models import MyModel
class MyModelTestCase(TestCase):
fixtures = ['my_model_fixtures.json']
def setUp(self):
my_model = MyModel.objects.get(id=1)
#mock.patch('app.utils.func')
def fake_func(self):
return mock.MagicMock(headers={'content-type': 'text/html'},
status_code=2000,
content="Fake 200 Response"))
def test_my_model(self):
my_model.call_func()
... # and asserting the parameters returned by func
When I run the test the mock function fake_func() is avoided and the real func() is called instead. I guess the scope in the mock.patch decorator might be wrong, but I couldn't find a way to make it work. What should I do?
There are three problems with your code:
1) As Daniel Roseman mentioned, you need to patch the module where the function is called, not where it is defined.
2) In addition, you need to decorate the test method that will actually be executing the code that calls the mocked function.
3) Finally, you also need to pass the mocked version in as a parameter to your test method, probably something like this:
fake_response = mock.MagicMock(headers={'content-type': 'text/html'},
status_code=2000,
content="Fake 200 Response"))
class MyModelTestCase(TestCase):
fixtures = ['my_model_fixtures.json']
def setUp(self):
my_model = MyModel.objects.get(id=1)
#mock.patch('app.models.func', return_value=fake_response)
def test_my_model(self, fake_response): # the mock goes in as a param or else you get number of arguments error!
my_model.call_func()
self.assertTrue(fake_response.called)
As the docs explain, you need to mock func in the place it is called, not where it is defined. So:
#mock.patch('app.models.func')

Django UnitTest with Mock

I am writing an Unit-Test for a Django class-based view.
class ExampleView(ListView):
def get_context_data(self, **kwargs):
context = super(EampleView, self).get_context_data(**kwargs)
## do something else
def get_queryset(self, **kwargs):
return self.get_data()
def get_data(self):
call_external_API()
## do something else
The key issue is that call_external_API() in get_data().
When I am writing Unit-test, I don't really want to call external API to get data. First, that will cost my money; second, I can easily test that API in another test file.
I also can easily test this get_data() method by having an unit-test only for it and mock the output of call_external_API().
However, when I test this whole class-based view, I simply will do
self.client.get('/example/url/')
and check the status code and context data to verify it.
In this case, how do I mock this call_external_API() when I am testing the whole class-based view?
What your are looking for is patch from unittest.mock. You can patch call_external_api() by a MagicMock() object.
Maybe you want to patch call_external_api() for all your tests in class. patch give to you essentialy two way to do it
decorate the test class
use start() and stop() in setUp() and tearDown() respectively
Decorate a class by patch decorator is like decorate all test methods (see documentation for details) and the implementation will be very neat. Follow example assume that your view is in my_view module.
#patch("my_view.call_external_api", autospec=True)
class MyTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_get_data(self, mock_call_external_api):
self.client.get('/example/url/')
self.assertTrue(mock_call_external_api.called)
More sophisticate examples can be build and you can check how you call mock_call_external_api and set return value or side effects for your API.
I don't give any example about start and stop way to do it (I don't really like it) but I would like to spend some time on two details:
I assumed that in your my_view module you define call_external_api or you import it by from my_API_module import call_external_api otherwise you should pay some attention on Where to patch
I used autospec=True: IMHO it should be used in every patch call and documentation explain why very well
You can mock the call_external_api() method when testing the classed based view with something like this:
import modulea
import unittest
from mock import Mock
class ExampleTestCase(unittest.TestCase):
def setUp(self):
self.call_external_api = modulea.call_external_api
def tearDown(self):
modulea.call_external_api = self.call_external_api
def get_data(self):
modulea.call_external_api = Mock(return_value="foobar")
modulea.call_external_api()
## do something else

Django ORM - mock values().filter() chain

I am trying to mock a chained call on the Djangos model.Manager() class. For now I want to mock the values() and filter() method.
To test that I created a little test project:
Create a virtual environment
Run pip install django mock mock-django nose django-nose
Create a project django-admin.py startproject mocktest
Create an app manage.py startapp mockme
Add django_nose and mocktest.mockme to INSTALLED_APPS (settings.py)
Add TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' to settings.py
To verfiy that everything is setup correctly I ran manage.py test. One test is run, the standard test Django creates when you create an app.
Next thing I did was to create a very simple model.
mockme/models.py
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=50)
Next thing I did was to create a simple function that uses MyModel. That's the function I want to test later.
mockme/functions.py
from models import MyModel
def chained_query():
return MyModel.objects.values('name').filter(name='Frank')
Nothing special is happening here. The function is filtering the MyModel objects to find all instances where name='Frank'. The call to values() will return a ValuesQuerySet which will only contain the name field of all found MyModel instances.
mockme/tests.py
import mock
from django.test import TestCase
from mocktest.mockme.models import MyModel
from mocktest.mockme.functions import chained_query
from mock_django.query import QuerySetMock
class SimpleTest(TestCase):
def test_chained_query(self):
# without mocked queryset the result should be 0
result = chained_query()
self.assertEquals(result.count(), 0)
# now try to mock values().filter() and reeturn
# one 'Frank'.
qsm = QuerySetMock(MyModel, MyModel(name='Frank'))
with mock.patch('django.db.models.Manager.filter', qsm):
result = chained_query()
self.assertEquals(result.count(), 1)
The first assertEquals will evaluate as successful. No instances are returned since the model Manager is not mocked yet. When the second assertEquals is called I expect result to contain the MyModel instance I added as return value to the QuerySetMock:
qsm = QuerySetMock(MyModel, MyModel(name='Frank'))
I mocked the filter() method and not the values() method since I found it'll be the last evaluated call, though I am not sure.
The test will fail because the second result variable won't contain any MyModel instances.
To be sure that the filter() method is really mocked I added a "debug print" statement:
from django.db import models
print models.Manager.filter
which returned:
<SharedMock name='mock.iterator' id='4514208912'>
What am I doing wrong?
Try this:
import mock
from mocktest.mockme.models import MyModel
class SimpleTest(TestCase):
def test_chained_query(self):
my_model_value_mock = mock.patch(MyModel.objects, 'value')
my_model_value_mock.return_value.filter.return_value.count.return_value = 10000
self.assertTrue(my_model_value_mock.return_value.filter.return_value.count.called)
#Gin's answer got me most of the way there, but in my case I'm patching MyModel.objects, and the query I'm mocking looks like this:
MyModel.objects.filter(arg1=user, arg2=something_else).order_by('-something').first()
so this worked for me:
#patch('MyModel.objects')
def test_a_function(mock, a_fixture):
mock.filter.return_value.order_by.return_value.first.return_value = a_fixture
result = the_func_im_testing(arg1, arg2)
assert result == 'value'
Also, the order of the patched attributes matters, and must match the order you're calling them within the tested function.

Categories