This is the second part of what I started here.
You can read the first post of Testing With Python: The Django Way to understand this one.
Now where were we?
The last time we left at test data. Now we will delve into requests.
GET Requests
The easiest of your views to test are the ones that only handle GET requests. All you have to do is retrieve the correct item from the database and display the information. Let's use the canonical book/author/publisher example. Suppose we're testing a view that displays the details for a particular book.
POST requests
You can read the first post of Testing With Python: The Django Way to understand this one.
Now where were we?
The last time we left at test data. Now we will delve into requests.
GET Requests
The easiest of your views to test are the ones that only handle GET requests. All you have to do is retrieve the correct item from the database and display the information. Let's use the canonical book/author/publisher example. Suppose we're testing a view that displays the details for a particular book.
def test_book_detail(self):
testbook = Book.objects.all()[0]
url = '/books/id/' + str(testbook.id)
response = self.client.get(url)
self.assertContains(response, testbook.title)
The first thing I want to stress, is that I think it's important that no specific test data is used in the tests. Always try to select objects you're going to use for your tests in a generic way. This way it won't matter how your test data gets moved around or changed over time. In this example I just picked the first book returned in the queryset for all books. What you don't want to do is something like this:
testbook = Book.objects.get(title="The Test Book")
That assumes your test database has a book with that title in it, which it may or may not. Over time it's bound to get mighty confusing trying to keep the data in your test database synchronized with the data in your tests, so it's best to save yourself the headache.
The Django TestCase class comes with a built in client to make all your requests. For a GET request all you have to do is pass it a URL and it will return the response generated by that request. The TestCase class also defines some new assertions in addition to the standard ones Python provides. assertContains, used in this example, takes a response object and verifies that it has a particular piece of text in it; in this case, the book's title. Optionally, you can specify a particular response code, and number of repititions for the piece of text. If you're expecting a particular url to not be found you might test:
badurl = '/books/id/notabookid'
response = self.client.get(badurl)
self.assertContains(response, "Book not found", status_code=404)
POST requests
Of course all of this data has to get into your database somehow. You know you don't want to put it all there yourself, so you're probably going to have your users do it. This likely means you'll have some forms on your site that you want them to submit. For our example, let's pretend authors are submitting their own books to the site. Let's assume a few things about how this function works, so we know what we're testing.
- The user must be logged in to submit a book
- A book requires a title, an author, and an ISBN field
- Upon successfully submitting a book, we want to redirect the user to the detail page for that book.
- The ID field for the Book model is auto-incremented (Django's default)
Simple enough, right? Django provides all the tools you need to test every aspect of this function.
def test_addbook(self):
url = '/books/add/'
author = Author.objects.all()[0]
user = author.user
# User not logged in
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
self.client.login(username=user.username, password='password')
# Valid user
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# Invalid Form
response = self.client.post(url, {'title': "Book Title",
'author_id': author.id,
'ISBN': "invalid ISBN"})
self.assertFormError(response,
"BookSubmitForm",
'ISBN',
"ISBN field must contain a number")
# Valid submission
newid = Book.objects.aggregate(Max('id'))['id__max'] + 1
redirect = '/books/id/%d' % newid
response = self.client.post(url, {'title': "Test book",
'author_id': author.id,
'ISBN': 123456},
follow=True)
self.assertRedirects(response, redirect)
self.assertContains(response, "Test book")
First you'll want to test a GET request for the url, to make sure the user can see the form properly to fill it in. Also check that the form is not visible to users who aren't logged in. In this case, that means simply making sure the response returns a 403 Forbidden status code. To log in as a user, simply use the self.client.login function with the appropriate name and password. It's easy enough to pull a random user from the ones you set up in the testsetup function, but I've violated my own rule a little by specifying a value for the user's password. In fact, I'm not sure there's any other way to do this. For the sake of simplicity, I'm just giving all of the users created in testsetup the same password. For POST requests, simply add a dictionary with all the required form fields and values. You can make sure the form validation is catching errors properly by submitting an invalid form and using the assertFormError assertion to verify that the appropriate form validation error occurs. This assertion takes 4 arguments: the response, the name the template uses for the form, the form field in question, and the error text (or a list of strings for multiple errors) that is expected. Finally, since the id for new books is just auto-incremented, we can assume the id for the book we create will be one more than highest one in the database yet. By plugging this into the book detail url, we have the address of the page we'll be redirected to when the book is successfully submitted. If you want to use assertContains or anything else to check the contents of the final page you get redirected to, be sure to add follow=True in the post function. This way it will return the final response, not the intermediate redirect response.
I suppose now is a good time to mention that the Django test runner will return the database to its original state (after populated by the setUp method) after each test. That means that any books you add during the test__addbook method will be deleted when that test is over. You won't have access to them in, for example, test_deletebook that you might write afterwards, so keep that in mind.
So this is what in a nutshell should get you started.
I've done these basic things to get myself up-to speed. Hope you'll find this useful too.Like I said, the documentation is up to Django's usual high standards, so definitely make the most of it.
Comments
Post a Comment