Mocking forms in view unit tests - django

I can't seem to be able to mock the behaviour of a form when unit testing views.
My form is a simple ModelForm and resides in profiles.forms. The view is (again) a simple view that checks whether the form is valid and then redirects.
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from profiles.forms import ProfileForm
def home(request):
form = ProfileForm()
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
profile = form.save()
return HttpResponseRedirect(reverse("thanks"))
My test looks like this:
class TestViewHomePost(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_form(self):
with mock.patch('profiles.views.ProfileForm') as mock_profile_form:
mock_profile_form.is_valid.return_value = True
request = self.factory.post(reverse("home"), data={})
response = home(request)
logger.debug(mock_profile_form.is_valid.call_count) # "0"
is_valid is not being called on the mock, which means ProfileForm is not patched.
Where did I make a mistake?

I was able to fix mocking is_valid as following:
def test_form(self):
with mock.patch('profiles.views.ProfileForm.is_valid') as mock_profile_form:
mock_profile_form.return_value = True
request = self.factory.post(reverse("home"), data={})
response = home(request)
note: and you could use mock_profile_form.assert_called_once() to check if the mock has been called.

Related

Set Django's RequestFactory() method to equal 'POST'

Example view function:
def example_view(request):
if request.method == "POST":
print(request.session['Key'])
return HttpResponse("Success")
Test case to test view:
from django.contrib.sessions.backends.db import SessionStore
from django.test import RequestFactory, TestCase
from website.views import example_view
class ExampleTestCase(TestCase):
def test_example(self):
# Create request and session
request = RequestFactory()
request.session = SessionStore()
request.session['Key'] = "Value"
response = example_view(request)
self.assertEquals(response.status_code, 200)
urls.py file in case anyone asks for it:
urlpatterns = [
path('example', views.example_view, name="example_view"),
]
Error Response:
AttributeError: 'RequestFactory' object has no attribute 'method'
Without:
if request.method == 'POST':
this works as expected.
How do I set the request.method equal to post?
RequestFactory gives you a factory, not a request, to get the request you must call the factory as you'd do with the Django testing client:
factory = RequestFactory()
request = factory.post('/your/view/url')
response = example_view(request)

Django forms which needs the request , makes testing harder?

I have many django forms in which I pass the request as kwarg.
I've just started dive into testing, and it seems that testing forms which require the request as argument makes the testing harder. As I have to somehow create a request and I cant test my forms without it.
So is it best to avoid passing the request to the form at all? Or another workaround?
The reason I do that on first place is that sometimes I need request.user, or request.session and do some cleaning/setting based on that info in the form.
UPDATE:
This is an example form:
class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
self.user = self.request.user
def clean(self):
# Here I have some cross session-field validation
if self.request.session['has_response'] and self.cleaned_data('status') == 'NEW':
raise ValidationError()
def save(self, commit=False):
self.instance.user = self.user
return super(OrderForm, self).save(commit=True)
class Meta:
model = Order
fields = ('address', 'city', 'status', ) # more fields
The view code is simple:
form = OrderForm(request.POST, request=request)
The Order model also have a clean() method with some validation logic.
The session is populated at most during the user login.
The point it I need the session/user there.
But most important, the question - is it a bad design to pass the request and session to the form, considering options for testing this form? I find it more logical when the form take care for saving the object, including the request.user. But maybe I should try to split that between the form and view?
Passing request to the form is okay if you need it in the clean() method. You can use a request/session/user in a test like this:
from django.test import TestCase, Client
from django.test.client import RequestFactory
from django.contrib.auth.models import AnonymousUser, User
from .views import my_view
from .forms import MyForm
from django.contrib.sessions.middleware import SessionMiddleware
# If Python >= 3.4
from unittest.mock import patch, MagicMock
# Else
from mock import patch, MagicMock
class SimpleTest(TestCase):
def setUp(self):
# Create a RequestFactory accessible by the entire class.
self.factory = RequestFactory()
# Create a new user object accessible by the entire class.
self.user = User.objects.create_user(username='username',
email='email', password='password')
def test_my_view(self):
# Create an instance of a GET request.
request = self.factory.get('/my-url/')
# Middleware is not supported so simulate a
# logged-in user by setting request.user.
request.user = self.user
# Or add anonymous user to request.
request.user = AnonymousUser()
# Test view() at '/my-url/'
response = my_view(request)
self.assertEqual(response.status_code, 200)
#patch('app.models.ModelName.save', MagicMock(name="save"))
def test_my_form_view_with_factory(self):
# Set up form data.
form_data = {'something': 'something'}
# Create an instance of a POST request.
request = self.factory.post('/my-form-url/', form_data)
# Simulate logged-in user
request.user = self.user
# Setup session.
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
# Or you should just be able to do
request.session['somekey'] = 'test'
request.session.save()
# Get response from form view, and test passing
# request/data to form.
form = MyForm(request=request, data=form_data)
response = my_form_view(request)
self.assertTrue(form.is_valid())
self.assertEqual(response.status_code, 200)
# If model form you can do
self.assertTrue(ModelName.save.called)
#patch('app.models.ModelName.save', MagicMock(name="save"))
def test_my_form_view_with_client(self):
# Use Client instead of RequestFactory.
self.client = Client()
# Login with Client.
self.client.login(username='username', password='password')
# Set up form data.
form_data = {'something': 'something'}
# Get/set session.
session = self.client.session
session['somekey'] = 'test'
session.save()
# Get response with Client.
response = self.client.post('/my-form-url/', form_data)
self.assertEqual(response.status_code, 200)
# If model form you can do
self.assertTrue(ModelName.save.called)
Should give a general idea of what you can do, not specifically tested.

Django testing the html of your homepage against the content of a response

If I have a test like so...
def test_home_page_returns_correct_html(self):
request = HttpRequest()
response = home_page(request)
expected_html = render_to_string('home.html', request=request)
self.assertEqual(response.content.decode(), expected_html)
As soon as I add a form in curly braces, e.g. {{ form }} The above test will fail as the .html file will not render the form only the response will. Thus causing the assertion to not match. Is there a way round this so that I can still test my html against the response?
You can pass a form instance to the render_to_string function:
from django.template import RequestContext
from django.template.loader import render_to_string
form = ModelForm()
context = RequestContext(request, {'form': form})
expected_html = render_to_string('home.html', context)
Usually what I do is splitting this kind of test into several other tests, like this:
Using from django.test import TestCase
def setUp(self):
self.user = User.objects.create_user('john', 'john#doe.com', '123')
self.client.login(username='john', password='123')
self.response = self.client.get(r('home'))
First test which template was used:
def test_template(self):
self.assertTemplateUsed(self.response, 'home.html')
Test the form:
def test_has_form(self):
form = self.response.context['form']
self.assertIsInstance(form, CreateModelForm)
And then I test the key parts of the HTML:
def test_html(self):
self.assertContains(self.response, '<form')
self.assertContains(self.response, 'type="hidden"', 1)
self.assertContains(self.response, 'type="text"', 2)
self.assertContains(self.response, 'type="radio"', 3)
self.assertContains(self.response, 'type="submit"', 1)
Finally if the rendered template has a csrf token:
def test_csrf(self):
self.assertContains(self.response, 'csrfmiddlewaretoken')

Set cookie in django-cms plugin

Using Django 1.5.5, Django-CMS 2.4.2
I wrote a plugin for django-cms *(cms.plugin_base.CMSPluginBase)*. Plugin create a some form, that works fine. But I get a problem - after submitting a form - how can I set a cookie ?
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from poll.models import PagePoll
from poll.forms import PollForm
from django.http import HttpResponse
class PollPlugin(CMSPluginBase):
""" upload a poll """
model = PagePoll
name = u'Poll'
render_template = u"plugins/page_poll.html"
def render(self, context, instance, placeholder):
#upload poll form
request = context['request']
form = PollForm(instance.poll, request.POST)
#validate the form
if request.method == 'POST':
if form.is_valid():
form.save()
#=SAVE COOKIES HERE=
else:
form = PollForm(instance.poll)
context['form'] = form
return context
plugin_pool.register_plugin(PollPlugin)
The answer was found here:
https://djangosnippets.org/snippets/2541/
The problem was that cms-plugin can't return a responce object, where cookies need to bet set... So the trick is to throw a custom exception with HttpResponceRedirect

Django testing stored session data in tests

I have a view as such:
def ProjectInfo(request):
if request.method == 'POST':
form = ProjectInfoForm(request.POST)
if form.is_valid():
# if form is valid, iterate cleaned form data
# and save data to session
for k, v in form.cleaned_data.iteritems():
request.session[k] = v
return HttpResponseRedirect('/next/')
else:
...
else:
...
And in my tests:
from django.test import TestCase, Client
from django.core.urlresolvers import reverse
from tool.models import Module, Model
from django.contrib.sessions.models import Session
def test_project_info_form_post_submission(self):
"""
Test if project info form can be submitted via post.
"""
# set up our POST data
post_data = {
'zipcode': '90210',
'module': self.module1.name,
'model': self.model1.name,
'orientation': 1,
'tilt': 1,
'rails_direction': 1,
}
...
self.assertEqual(response.status_code, 302)
# test if 'zipcode' in session is equal to posted value.
So, where the last comment is in my test, I want to test if a specific value is in the session dict and that the key:value pairing is correct. How do I go about this? Can I use request.session?
Any help much appreciated.
According to the docs:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
# Issue a GET request.
self.client.get('/customer/details/')
session = self.client.session
self.assertEqual(session["somekey"], "test")