Testing in Django with DRY code and Factory Libraries
The “Don’t Repeat Yourself” (DRY) principle is a software development principle that states that code should not be duplicated. It’s a good practice to follow this principle when writing test cases in Django, as it helps to keep your tests maintainable and easy to understand.
Here’s an example of how you can write Django test cases using the DRY principle:
from django.test import TestCase
class MyTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(username='testuser')
self.post = Post.objects.create(title='test post', author=self.user)
def test_post_title(self):
self.assertEqual(self.post.title, 'test post')
def test_post_author(self):
self.assertEqual(self.post.author, self.user)
In the example above, the setUp
method is used to create a test user and post. These objects are then used in both test methods, test_post_title
and test_post_author
, eliminating the need for duplication of code.
You can also use helper methods to avoid duplication of test code. For example, you can create a helper method that creates a test post, and then call that method in multiple test methods:
from django.test import TestCase
class MyTestCase(TestCase):
def create_test_post(self):
user = User.objects.create(username='testuser')
return Post.objects.create(title='test post', author=user)
def test_post_title(self):
post = self.create_test_post()
self.assertEqual(post.title, 'test post')
def test_post_author(self):
post = self.create_test_post()
self.assertEqual(post.author.username, 'testuser')
Finally, you can use inheritance to avoid code duplication. For example, you can create a base test class that contains common setUp and helper methods, and then inherit from that class in your other test classes.
class BaseTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(username='testuser')
def create_test_post(self):
return Post.objects.create(title='test post', author=self.user)
class MyTestCase(BaseTestCase):
def test_post_title(self):
post = self.create_test_post()
self.assertEqual(post.title, 'test post')
def test_post_author(self):
post = self.create_test_post()
self.assertEqual(post.author.username, 'testuser')
By following the DRY principle when writing test cases, you can make your test code more maintainable and easier to understand, which will make it easier to identify and fix bugs when they occur.
More Complex Test
Here’s an example of a more complex test case that includes multiple test methods and uses the DRY principle:
from django.test import TestCase
from django.urls import reverse
from myapp.models import Book, Author
class BookTestCase(TestCase):
def setUp(self):
self.author = Author.objects.create(name='J.K. Rowling')
self.book = Book.objects.create(title='Harry Potter and the Philosopher\'s Stone', author=self.author)
self.book_data = {'title': 'Harry Potter and the Chamber of Secrets', 'author': self.author.id}
def test_book_detail_view(self):
response = self.client.get(reverse('book_detail', args=[self.book.id]))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['book'], self.book)
def test_create_book(self):
response = self.client.post(reverse('create_book'), data=self.book_data)
self.assertEqual(response.status_code, 302)
self.assertEqual(Book.objects.count(), 2)
self.assertEqual(Book.objects.last().title, 'Harry Potter and the Chamber of Secrets')
def test_update_book(self):
response = self.client.post(reverse('update_book', args=[self.book.id]), data={'title': 'Harry Potter and the Sorcerer\'s Stone'})
self.assertEqual(response.status_code, 302)
self.assertEqual(Book.objects.first().title, 'Harry Potter and the Sorcerer\'s Stone')
def test_delete_book(self):
response = self.client.post(reverse('delete_book', args=[self.book.id]))
self.assertEqual(response.status_code, 302)
self.assertEqual(Book.objects.count(), 0)
In this example, the setUp
method is used to create an author and a book, and also a data for creating a book. The test methods use the self.client
attribute provided by the TestCase class to simulate HTTP requests to the views and check the responses.
The test_book_detail_view
method uses the reverse function to generate the URL for the book detail view and then uses the self.client.get
method to simulate a GET request to that URL. It then checks the status code and context data of the response.
The test_create_book
, test_update_book
, test_delete_book
methods use the reverse function and the self.client.post
method to simulate POST requests to the create, update, and delete views. They then check the status code, the count of books in the database and the last book’s title.
This example shows how you can use the DRY principle when writing test cases for multiple views and models. By sharing the setup code and data between test methods, you can keep your test code more maintainable and easy to understand.
It’s worth noting that you can use libraries such as factory_boy
or model_mommy
to create test data instead of writing long set
Factory Library
Here’s an example of how you can use factory_boy to create test data for a Django test case:
import factory
from django.test import TestCase
from myapp.models import Book, Author
class AuthorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Author
name = 'J.K. Rowling'
class BookFactory(factory.django.DjangoModelFactory):
class Meta:
model = Book
title = 'Harry Potter and the Philosopher\'s Stone'
author = factory.SubFactory(AuthorFactory)
class BookTestCase(TestCase):
def setUp(self):
self.author = AuthorFactory()
self.book = BookFactory()
def test_book_title(self):
self.assertEqual(self.book.title, 'Harry Potter and the Philosopher\'s Stone')
def test_book_author(self):
self.assertEqual(self.book.author, self.author)
In this example, the AuthorFactory
and BookFactory
classes are defined using the factory_boy
DjangoModelFactory
. These classes specify the model they are creating instances of and the default values for the fields.
The setUp
method uses these factories to create an author and a book. The test methods use these objects to check the values of the fields.
By using factory_boy
, you can easily create instances of models for testing, and it can help you to set up test data more quickly and efficiently. Also, you can easily customize the factory’s default fields, and it’s easy to generate large sets of test data.
It’s worth noting that factory_boy
is highly customizable and you can use various strategies to generate data, such as Sequence
, Iterator
and SubFactory
to create more advanced test data scenarios.