Someone has probably already developed a technique for relieving the tedium for the following idiomatic unit test:
GET a url with form data already populated
POST a revised form with one or more fields edited
Check response (profit!)
Step 2 is the most tedious, cycling through the form fields. Are there any time-saving hacks for testing Django forms?
[Update: I'm not testing Django forms handling. I'm verifying that my application produces correct responses when a user makes changes to a form. This is an application which processes clinical information, hence a lot of possible responses to test.]
It depends what you are trying to test. I would target your tests a bit more finely than it sounds like you are doing.
If the code you need to test is the form validation logic, then I would simply instantiate the form class directly in your tests, pass it various data dictionaries and call .is_valid(), check for the proper errors or lack thereof. No need to involve HTML or HTTP requests.
If it's view logic (which IMO should be minimized) that you are testing, you will probably want to use the test client, but you shouldn't need to do multi-stage tests or very many tests at this level. In testing view logic I wouldn't scrape HTML (that's testing templates), I'd use response.context to pull out the form object from the context.
If what you want to test is that the templates contain the proper HTML to make the form actually work (in order to catch errors like forgetting to include the management form for a formset in the template), I use WebTest and django-webtest, which parse your HTML and make it easy to fill in field values and submit the form like a browser would.
You can use response.context and form.initial to get the values you need to post:
update_url = reverse('myobject_update',args=(myobject.pk,))
# GET the form
r = self.client.get(update_url)
# retrieve form data as dict
form = r.context['form']
data = form.initial # form is unbound but contains data
# manipulate some data
data['field_to_be_changed'] = 'updated_value'
# POST to the form
r = self.client.post(update_url, data)
# retrieve again
r = self.client.get(update_url)
self.assertContains(r, 'updated_value') # or
self.assertEqual(r.context['form'].initial['field_to_be_changed'], 'updated_value')
django-webtest is perfect for such tests:
from django_webtest import WebTest
class MyTestCase(WebTest):
def test_my_view(self)
form = self.app.get('/my-url/').form
self.assertEqual(form['my_field_10'].value, 'initial value')
form['field_25'] = 'foo'
response = form.submit() # all form fields are submitted
In my opinion it is better than twill for django testing because it provides access to django internals so native django's response.context, response.templates, self.assertTemplateUsed and self.assertFormError API is supported.
On other hand it is better than native django test client because it has much more powerful and easy API.
I'm a bit biased ;) but I believe that django-webtest is now the best way to write django tests.
It's not clear but one guess is that you have tests like this.
class TestSomething( TestCase ):
fixtures = [ "..." ]
def test_field1_should_work( self ):
response= self.client.get( "url with form data already populated" )
form_data = func_to_get_field( response )
form_data['field1']= new value
response= self.client.post( "url", form_data )
self.assert()
def test_field2_should_work( self ):
response= self.client.get( "url with form data already populated" )
form_data = func_to_get_field( response )
form_data['fields']= new value
response= self.client.post( "url", form_data )
self.assert()
First, you're doing too much. Simplify.
class TestFormDefaults( TestCase ):
fixtures = [ "some", "known", "database" ]
def test_get_should_provide_defaults( self ):
response= self.client.get( "url with form data already populated" )
self.assert(...)
The above proves that the defaults populate the forms.
class TestPost( TestCase ):
fixtures = [ "some", "known", "database" ]
def test_field1_should_work( self ):
# No need to GET URL, TestFormDefaults proved that it workd.
form_data= { expected form content based on fixture and previous test }
form_data['field1']= new value
response= self.client.post( "url", form_data )
self.assert()
Don't waste time doing a "get" for each "post". You can prove -- separately -- that the GET operations work. Once you have that proof, simply do the POSTs.
If you POSTS are highly session-specific and stateful, you can still do a GET, but don't bother parsing the response. You can prove (separately) that it has exactly the right fields.
To optimize your resting, consider this.
class TestPost( TestCase ):
fixtures = [ "some", "known", "database" ]
def test_many_changes_should_work( self ):
changes = [
( 'field1', 'someValue', 'some expected response' ),
( 'field2', 'someValue' ),
...
]
for field, value, expected in changes:
self.client.get( "url" ) # doesn't matter what it responds, we've already proven that it works.
form_data= { expected form content based on fixture and previous test }
form_data[field]= value
response self.client.post( "url", form_data )
self.assertEquas( expected, who knows what )
The above will obviously work, but it makes the number of tests appear small.
Think carefully about why you need to unit-test this. Forms are part of the core Django functionality, and as such are very well covered by Django's own unit tests. If all you're doing is basic create/update, which from your question it sounds like is the case, I don't see any reason to write unit tests for that.
I don't see how or why you need unit tests for this. Sounds to me like you're testing for (and correcting) possible user input, which is covered very simply with Django's form validation (and model validation in 1.2)
I'd recommed you to take a look into acceptance testing level tools like robot test framework or letucce which are thought just for you want to do "I'm verifying that my application produces correct responses when a user makes changes to a form", that sounds more like acceptance (black-box) testing than unit-testing.
For instace, Robot let you to define your tests in tabular form, you define the workflow once and then you can define easily a bunch of tests that exercise the workflow with different data.
You are possibly looking for tools that do front end testing like twill or selenium
Both of these generate python code, that can be included within the django tests, so when you run tests, it opens the urls, posts the data and inspects whatever you want!
It should help you to see these tests written for selenium, for an open source reusable django app.
Related
I am wondering if someone could help me. I am trying to test some views in a Django restaurant bookings system app I have created. I am doing well with jut testing the views but now I want to test the CRUD functionality of certain pages. In particular the create a booking on the bookings page and then redirect it back to the home page once the booking was successful (as is what happens on the site)
I just can't seem to figure out how to do it. Here is my current code. If someone could point me in the right direction that would be great. Thanks
setUp:
class TestViews(TestCase):
"""
Testing of the views taken from
the views.py file.
All HTTP testing includes the base.html template
as well as the view being tested to make sure everything
is being tested as it would appear for a user
"""
def setUp(self):
testing_user = User.objects.create_user(
username='JohnSmith',
first_name='John',
last_name='Smith',
email='johnsmith#email.com',
password='RandomWord1'
)
Booking.objects.create(
user=testing_user,
name='John Smith',
email_address='johnsmith#email.com',
phone='123654789',
number_of_people='2',
date='2022-10-20',
time='19:00',
table='Window',
occasion='none'
)
test:
def test_add_booking(self):
self.log_in()
response = self.client.post('/bookings', {Booking: Booking})
self.assertRedirects(response, '/')
You can check the status's code of your response, then also compare the response content.
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No bookins.")
Or even to be more precisely self.assertQuerysetEqual(response.context["the name"], ["with real content"]
I just started using Factory boy in my test and it's working great.
I'd like to test crud view, so at some point, I need to post data (in json) for create and update action.
I'd like my test content to be something like:
a = self.client.post(
my_url,
json.dumps(my_factory.stub()),
content_type="application/json")
assert a.status_code == 403
which is not working obviously.
I get <factory.containers.StubObject object at 0x7ffa34e375d0> is not JSON serializable
Is there any way to do that? Or I need to fill all the post data myself?
(I'm not testing the form itself or the validation, just the post response.
Cheers
<User> is a django model and doesn't know how to represent itself as JSON. You need to use a serializer like ModelSerializer from rest-framework or the django builtin serializing capabilities:
tests.py
from django.core import serializers
data = serializers.serialize('json', my_factory.stub(), fields=('id'))
a = self.client.post(
my_url,data,
content_type="application/json")
assert a.status_code == 403
Please note that any test using the django test client would be considered an integration test by most.
This does not mean that it is a bad test, I have many tests just like your's, however they are integration tests.
If you would like to make this more of a unit-test try setup_view from here.
I'm writing some tests for my Django Rest Framework and trying to keep them as simple as possible. Before, I was creating objects using factory boy in order to have saved objects available for GET requests.
Why are my POST requests in the tests not creating an actual object in my test database? Everything works fine using the actual API, but I can't get the POST in the tests to save the object to make it available for GET requests. Is there something I'm missing?
from rest_framework import status
from rest_framework.test import APITestCase
# from .factories import InterestFactory
class APITestMixin(object):
"""
mixin to perform the default API Test functionality
"""
api_root = '/v1/'
model_url = ''
data = {}
def get_endpoint(self):
"""
return the API endpoint
"""
url = self.api_root + self.model_url
return url
def test_create_object(self):
"""
create a new object
"""
response = self.client.post(self.get_endpoint(), self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, self.data)
# this passes the test and the response says the object was created
def test_get_objects(self):
"""
get a list of objects
"""
response = self.client.get(self.get_endpoint())
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
# this test fails and says the response is empty [] with no objects
class InterestTests(APITestCase, APITestMixin):
def setUp(self):
self.model_url = 'interests/'
self.data = {
'id': 1,
'name': 'hiking',
}
# self.interest = InterestFactory.create(name='travel')
"""
if I create the object with factory boy, the object is
there. But I don't want to have to do this - I want to use
the data that was created in the POST request
"""
You can see the couple lines of commented out code which are the object that I need to create through factory boy because the object does not get created and saved (although the create test does pass and say the object is created).
I didn't post any of the model, serializer or viewsets code because the actual API works, this is a question specific to the test.
First of all, Django TestCase (APITestCase's base class) encloses the test code in a database transaction that is rolled back at the end of the test (refer). That's why test_get_objects cannot see objects which created in test_create_object
Then, from (Django Testing Docs)
Having tests altering each others data, or having tests that depend on another test altering data are inherently fragile.
The first reason came into my mind is that you cannot rely on the execution order of tests. For now, the order within a TestCase seems to be alphabetical. test_create_object just happened to be executed before test_get_objects. If you change the method name to test_z_create_object, test_get_objects will go first. So better to make each test independent
Solution for your case, if you anyway don't want database reset after each test, use APISimpleTestCase
More recommended, group tests. E.g., rename test_create_object, test_get_objects to subtest_create_object, subtest_get_objects. Then create another test method to invoke the two tests as needed
I read the official doc for generating the unit tests but it is too messy. I have to generate a unit test cases for the AdminPasswordChangeForm, and two defined function in views.py which are changing the status of is_staff and is_active.
I have also wrote simple test cases to register an user. Follow is the method defined in views.py (which are updating the password using AdminPasswordChangeForm)
def user_change_password(request, id):
user = User.objects.get(pk=id)
form = AdminPasswordChangeForm(user, request.POST)
if form.is_valid():
new_user = form.save()
msg = _('Password changed successfully.')
request.user.message_set.create(message=msg)
return HttpResponseRedirect('../../user/users')
else:
form = AdminPasswordChangeForm(user)
extra_context = {
'form': form,
'change': True
}
return direct_to_template(request,"users/user_password_change.html",
extra_context = extra_context)
There are lots of tutorials on how to write tests. Django Documentation (like always) does a great job of explaining what their framework offers in terms of testing. https://docs.djangoproject.com/en/dev/topics/testing/?from=olddocs
In addition there are many articles/slideshows covering WHAT to test.
http://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/
As far as HOW to test a view you have a couple of options
You can use the built in request factory to generate a request object and call your function directly. You can make sure a redirect is returned if appropriate or a template or whatever you are expecting.
Or you can make a request to the url in your urls.py file.
I mean to test AdminPasswordChangeForm instantiate it with a user object and a POST dictionary. Check to make sure it is creating the correct form, If you haven't customized it too much you can skip testing it becuase django does a great job of testing their code.
I'm trying to figure out how to test middleware in django. The middleware I'm writing logs in a user under certain conditions (if a key sent in email is valid). So obviously I'm dependent on django.contrib.auth and django.contrib.sessions.
I'm running into problems testing the login portion. I'm making a request like this:
user = User.objects.create_user('user', 'user#example.org', 'password')
key = LoginKey.objects.create(user=user)
request = self.factory.get('/', data={'auth_key': key.hash}) # self.factory is a RequestFactory()
self.middleware.process_request(request) # self.middleware is MyMiddleware()
That fails due to the session not being set. So next, I wrote a little snippet in my test class:
def make_session(self, request):
SessionMiddleware().process_request(request)
and that fails due to 'User' object has no attribute 'backend'. I'm not sure on the meaning of that, but I suspect I need to run all the middlewares I have installed.
I don't really want to make a fake view for this just to run a middleware, but I can't see another option at this point.
So I just wanted to know, before I have to chase this rabbit all the way down the hole, is there a way of doing this that doesn't require as much duct tape?
You should use the test client for this. That will ensure that the middleware is run and the session keys created.
response = self.client.get('/?auth_key=%s' % key.hash)
self.assertTrue(response.context['user'].is_authenticated()) # for example