Testing for Django Rest Framework with Factory Boy and Faker
Dec 22, 2018 · 4 Min Read · 0 Like · 5 Comments“I’m not a great programmer; I’m just a good programmer with great habits.” ― Kent Beck
Test Driven Development - in short TDD is a practice where you write the tests first then the actual code. It makes the code less vulnerable and it makes a testable software rather than writing the software, then test it.
Django Rest Framework - in short DRF is a powerful framework which provides RESTful API support over Django. It provides minimal ways to expose the RESTful API directly from model( or without it, along with many other features out-of-the box like authentication, permission, throttling and so on.).
As we will be testing our RESTful APIs though TDD, we need data which should simulate real world data. But writing these data in the code may not be efficient if its in large volume. Also it may take a long time to do so. So we need some tools which will generate those fake data for us. That is where Factory Boy and Python Faker by joke2k comes in.
Today we are going to discuss how we can use TDD in DRF with shortest implementation possible.
Preparation
As always, we need to prepare ourselves before starting something. Obviously we need to setup Django and Django Rest Framework in our local machine. Now, we can write a model in our system like this:
class Blog(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
body = models.TextField()
# and so on
Then, we need to write a serializer for serializing the Blog
objects:
class BlogSerializer(serializer.ModelSerializer):
class Meta:
model = Blog
Let’s write a small view, which takes slug as url parameter and returns the actual blog response:
from rest_framework.views import APIView
from rest_framework.response import Response
class BlogView(APIView):
def get(self, slug):
try:
blog = Blog.objects.get(slug=slug)
serializer = BlogSerializer(blog)
return Response(serializer.data, status=status.HTTP_200_OK)
except BlogDoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except Exception:
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Another view to get all Blogs:
from rest_framework.views import APIView
from rest_framework.response import Response
class BlogListView(APIView):
def get(self):
try:
blogs = Blog.objects.all()
serializer = BlogSerializer(blog, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception:
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Finally attach this view to an URL:
path('blog/<slug:title>/', BlogView.as_view(), name="blog-get"),
path('blogs/', BlogView.as_view(), name="blog-all"),
Thus our preparation for testing is complete. Now lets dive into testing.
Testing
For writing test cases, we need to create some files inside the app. Lets say, we have created test_blog.py
and inside it, the code should look like this:
from rest_framework.test import APITestCase
class BlogTest(APITestCase):
# some pieces of code
Here, we will be subclassing BlogTest from APITestCase
because it comes built in with self.client
which can be used for calling the API. Now lets setup a test:
from django.urls import reverse # https://docs.djangoproject.com/en/2.1/ref/urlresolvers/#reverse
class BlogTest(APITestCase):
def test_blog_not_found(self):
data = {'slug': 'random'}
response = self.client.get(reverse('blog-get'), data=data)
self.assertEqual(response.status_code, status.HTTP_400_NOT_FOUND)
Here we can see, if we call the API without creating a blog instance, it will return 404. Now lets create one:
def test_blog_found(self):
name = "TEST"
slug = "test"
body = "TEST TWO"
blog = Blog.objects.create(name=name, body=body, slug=slug)
ata = {'slug': slug}
response = self.client.get(reverse('blog-get'), data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get('slug'), slug)
# and so on
In this case, we should get 200 status and response slug should match our provided slug. As we are testing only one object, creating one should not be a problem. But how about a list objects(regarding our second view). Can we write the name
, slug
, body
variables for 100 times if we need to check for 100 objects?
Writing Factory
To overcome this, we can setup a library called factory boy which will generate Blog objects randomly. To install factory boy, we need to type:
pip install factory_boy
Now, to generate 100 Blog objects, we can setup a Factory Class which has Blog
in Meta
class:
class BlogFactory(factory.Factory):
class Meta:
model = Blog
To ensure each object is random, we can put some providers in attributes(named same as model fields):
class BlogFactory(factory.Factory):
class Meta:
model = Blog
name = factory.Faker('name')
slug = factory.Faker('slug')
body = factory.Faker('text')
Now we can use this factory in our test:
def test_hundrand_blogs(self):
for i in range(100):
BlogFactory()
response = self.client.get(reverse('blog-list'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 100)
# and so on
Pretty cool, right!! Now, let’s say we want to create a BlogFactory
instance which has title X. We can do that by passing title
keyword argument through BlogFactory
instance call:
def test_blog_X(self):
blog = BlogFactory(title='X')
response = self.client.get(reverse('blog-get'), data={'slug': blog.slug})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get('title'), "X")
# and so on
Finally, one more test case, where you want the X to be a random value. Its also doable using python faker by joke2k. We can install it using:
pip install Faker
And we can use it like this:
from faker import Faker
def test_blog_fake_name(self):
fake = Faker()
name = fake.name()
blog = BlogFactory(title=name)
response = self.client.get(reverse('blog-get'), data={'slug': blog.slug})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get('title'), name)
Conclusion
Testing is made easier by libraries like Factory boy, Faker etc. So we should use them write test cases faster.
Thats it. Thank you for reading. Let me know if you have any questions in comments section below.
Cheers!!
Last updated: Nov 08, 2024
I won't spam you. Unsubscribe at any time.