Patching(mocking) forms form in django tests - python

I tried to mock form with mock.patch and can`t. I have this code
forms.py
class CreatePostForm(object):
pass
views.py:
from forms import CreatePostForm
def doit():
print CreatePostForm()
and I want to to test this view in isolation. I tried to patch form with mock.patch and i wrote something like that:
tests.py:
from mock import patch
import views
with patch('forms.CreatePostForm') as Form:
views.doit()
I tried to google for solution and find nothing
Answered: thanks #dstanek for good answer and good sample of code

When you use patch you specify the target of the object you want to mock. This is usually the place where it is imported, not where it is defined.
This is because by the time your test runs the views module has already been imported. If you are importing the class like I'm doing in my example below then the views module will contain a reference to the forms.CreatePostForm. So changing forms.CreatePostForm would not change this reference. Things would be different if you imported the module as specified forms.CreatePostForm in your view.
I've included an extremely minimal example below.
forms.py
class CreatePostForm(object):
pass
views.py:
from forms import CreatePostForm
def doit():
print CreatePostForm()
tests.py:
from mock import patch
import views
with patch('views.CreatePostForm') as Form:
views.doit()

Related

Mock keeps calling real function

I'm trying to mock a function. When I try to mock the function core.use_cases.add_owner_to_place the mock doesn't work. It keeps printing "Ouch".
I've tried to test mocked_add_owner_to_place.called and it returns False.
Does anyone know why it keeps using the real function even if I mock it?
views.py:
from core.use_cases import add_owner_to_place
class CreatePlace(LoginRequiredMixin, FormView):
template_name = 'place/create_place.html'
form_class = PlaceForm
success_url = reverse_lazy('place_list')
def form_valid(self, form):
place = form.save()
add_owner_to_place(place, self.request.user)
return super(CreatePlace, self).form_valid(form)
tests.py:
from unittest.mock import patch, Mock
#patch('core.use_cases.add_owner_to_place')
#patch('core.forms.PlaceForm.is_valid')
#patch('core.forms.PlaceForm.save')
def test_save_should_be_called(self, mocked_save, mocked_is_valid, mocked_add_owner_to_place):
self.client.post(reverse('place_create'), data={})
self.assertTrue(mocked_save.called)
uses_cases.py:
def add_owner_to_place(place, user):
print('Ouch')
So, searching around and looking some codes on github, I found out that I need to mock from the view even if the function belongs to the use_cases module.
So my code now is:
tests.py
from unittest.mock import patch, Mock
#patch('core.views.add_owner_to_place')
#patch('core.forms.PlaceForm.is_valid')
#patch('core.forms.PlaceForm.save')
def test_save_should_be_called(self, mocked_save, mocked_is_valid, mocked_add_owner_to_place):
self.client.post(reverse('place_create'), data={})
self.assertTrue(mocked_save.called)
I know that this works, but now I need to search why it works. I'll explain it when I figure it out.

Expose a Django app's models at the module level

I've made a django app, which is designed to be easily pluggable, and only has 1 view, and 1 model that project planning to use the app need to be aware of.
For ease, I'd like to just make the view and model available from the app-level. So rather than:
from mything.views import MyView
from mything.models import MyModel
You can instead just do:
from mything import MyView, MyModel
I changed the __init__.py file in the app to be like this:
from .views import MyView
from .models import MyModel
Of course I get the old django.core.exceptions.AppRegistryNotReady raised, since it's attempting to run the models.py code before the apps are loaded.
So I came up with the following workaround, and I'm wondering if it's a reasonable pattern to use or not. Now in __init__.py I have:
def _expose_items():
from .views import MyView
from .models import MyModel
globals()['MyView'] = MyView
globals()['MyModel'] = MyModel
And in my app's apps.py:
from . import _expose_items
class MyThingConfig(AppConfig):
name = 'mything'
def ready(self):
_expose_items()
So now indeed, I can directly import the view and model from the outside. Is this useful, or horrible?
Most Django apps do not collect views or models to the top level module. Refer to django.contrib.auth as an example.
Most Django apps do not collect views or models to the top level module. Use clear documentation to demonstrate how to import from your app. Hacks including globals() will probably create more trouble than help.
Refer to django.contrib.auth as an example.

How to Lazy Load a model in a managers to stop circular imports?

In Django you can create managers for your models. I do this by added a new file called managers.py and in my model objects = MyManager().
To stop circular imports I do self.model. However, if I need to reference a different model in my manager i.e.
from models import SecondModel
second= SecondModel(name=test).save()
self.model(second=second)
I get the following error: ImportError: cannot import name SecondModel
So is there a way in Django to lazy load a model?
The currently accepted answer is deprecated as of Django 1.7; from this answer, you can adapt your code like this.
from django.apps import apps
class SomeModelManager(...):
...
def some_function(self):
model = apps.get_model(app_label='your_app', model_name='YourModel')
You have a few options:
1. Import by name
Django has a utility function for importing by string name so you don't need to import yourself. There are several methods available for this (see this question: Django: Get model from string?)
from django.db.models.loading import get_model
class SomeModelManager(...):
...
def some_function(self):
model = get_model('your_app', 'YourModel')
object = model()
2. Imports at the bottom
Add the import at the bottom of the managers.py file and make sure to simply import the module and not the models themselves.
So...
models.py:
import managers
class SomeModel(models.Model):
...
objects = managers.SomeModelManager()
managers.py
class SomeModelManager(...):
...
def some_function(self):
object = models.SomeOtherModel()
import models

Django Splitting models from models.py file

I am trying to separate all the models from models.py file.What I am doing is mentioned in this link. But Problem is My one model is django.contrib.auth.user and I am wring one function in models.py as follows to generate token.
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
So how do I import that thing in _init_.py file as we are importing model as
from myapp.models.foo import Foo
You should only have either models.py or models/__init__.py, and it seem like you have both. One of these modules will probably shadow the other, so don't have both (i.e. get rid of the models.py)
If I'm following your question correctly, you don't need to import User into __init__.py. You just need to import it in the file in which you declare create_user_profile. The import itself is standard:
from django.contrib.auth.models import User
You can't import a command, but for example importing the function defined above it, ensures running the connect call. Function calls in the body of the models.py file are also executed of the same raeson (i.e. models are imported).
# p.py:
print "hello" # a command executed while importing anything
def x(): # a definition that can be imported
pass
# python shell
>>> from p import x
hello
>>>

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