TutorialΒΆ

Note

You can clone this example and run the tests yourself from: https://github.com/ydaniv/django-rest-assured-demo.

Let’s take a look at an example from the Django documentation of a Weblog application:

from django.db import models


class Blog(models.Model):

    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name


class Author(models.Model):

    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name


class Entry(models.Model):

    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(auto_now=True)
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):              # __unicode__ on Python 2
        return self.headline

The above file will serve as the models.py file in this example application.

Say that we want to have a RESTful API endpoint for the Entry model. We’ll need a serializer for Entry objects, so this will serve as our serializers.py:

from rest_framework import serializers
from . import models


class Entry(serializers.ModelSerializer):

    class Meta:
        model = models.Entry

Now we’re ready to define our views. The following shall serve as views.py:

from rest_framework import viewsets
from . import models, serializers


class Entries(viewsets.ModelViewSet):

    queryset = models.Entry.objects.all()
    serializer_class = serializers.Entry

And hooking that viewset with URL’s, we’ll add a urls.py and define a router:

from django.conf.urls import url, include
from rest_framework import routers
from . import views


router = routers.DefaultRouter()
router.register(r'entries', views.Entries)


urlpatterns = [
    url(r'^', include(router.urls)),
]

And we’ll assume the pattern above is added to the project’s root urlpatterns under the prefix /api/, so that our endpoint will look like /api/entries/.

Now we have an API endpoint we can test. Yay!

To make things even easier we’ll create a factories.py file that will include factories for our models using Factory Boy:

import datetime
import factory
from factory import fuzzy
from . import models


class Blog(factory.DjangoModelFactory):

    class Meta:
        model = models.Blog

    name = factory.Sequence(lambda n: 'Blog {0}'.format(n))
    tagline = factory.Sequence(lambda n: 'Blog {0} tag line'.format(n))


class Author(factory.DjangoModelFactory):

    class Meta:
        model = models.Author

    name = factory.Sequence(lambda n: 'Author {0}'.format(n))
    email = factory.Sequence(lambda n: 'author{0}@example.com'.format(n))


class Entry(factory.DjangoModelFactory):

    class Meta:
        model = models.Entry

    blog = factory.SubFactory(Blog)
    headline = factory.Sequence(lambda n: 'OMG Headline {0}!'.format(n))
    body_text = fuzzy.FuzzyText(length=100)
    pub_date = datetime.date(2014, 11, 12)
    mod_date = datetime.date(2014, 11, 12)
    rating = fuzzy.FuzzyInteger(low=1, high=5, step=1)
    n_pingbacks = 0
    n_comments = 0

    @factory.post_generation
    def authors(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for author in extracted:
                self.authors.add(author)

This will make testing fun.

Let’s write the tests! This shall be our tests.py file:

from rest_assured.testcases import ReadWriteRESTAPITestCaseMixin, BaseRESTAPITestCase
from . import factories


class EntryAPITestCase(ReadWriteRESTAPITestCaseMixin, BaseRESTAPITestCase):

    base_name = 'entry' # this is the base_name generated by the DefaultRouter
    factory_class = factories.Entry
    update_data = {'rating': 5}

    def setUp(self):
       self.author = factories.Author.create()
       super(EntryAPITestCase, self).setUp()

    def get_object(self, factory):
        return factory.create(authors=[self.author])

    def get_create_data(self):
       return {'headline': 'Lucifer Sam',
               'body_text': 'is a song by British psychedelic rock band Pink Floyd.',
               'authors': [self.author.pk],
               'rating': 4,
               'n_pingbacks': 0,
               'n_comments': 0,
               'pub_date': datetime.date(2014, 11, 12),
               'blog': self.object.blog.pk}

And that’s it!

This simple class will make 5 tests if we’ll run:

$ python manage.py test

And will produce an output like such:

user@machine:~/project$ python manage.py test
Creating test database for alias 'default'...
.....
----------------------------------------------------------------------
Ran 5 tests in 0.155s

OK
Destroying test database for alias 'default'...

You can see the above example is not entirely trivial. We had to do some setup work to ensure we have a ready made Author instance. We also created dynamic getters for the main test object and the data dict used for the create request. In both cases this was required to obtain a lazy reference to the Author instance we created in setUp().

Say now our API is not public and requires authentication (token, session, etc.). We’ll need a user factory to mock authenticated requests. Let’s create that factory:

from django.contrib import auth


class User(factory.DjangoModelFactory):

    class Meta:

        model = auth.get_user_model()
        exclude = ('raw_password',)

    first_name = 'Robert'
    last_name = factory.Sequence(lambda n: 'Paulson the {0}'.format(n))
    email = factory.sequence(lambda n: 'account{0}@example.com'.format(n))
    username = 'mayhem'
    raw_password = '123'
    password = factory.PostGenerationMethodCall('set_password', raw_password)
    is_active = True

Our tests now will fail, since all responses will return a HTTP_401_UNAUTHORIZED status code. Which is great.

Assuming that User factory resides in the previous factories.py module, we add a user_factory attribute to our test case:

...
user_factory = factories.User
...

The full version of our tests.py now look like:

from rest_assured.testcases import ReadWriteRESTAPITestCaseMixin, BaseRESTAPITestCase
from . import factories


class EntryAPITestCase(ReadWriteRESTAPITestCaseMixin, BaseRESTAPITestCase):

    base_name = 'entry' # this is the base_name generated by the DefaultRouter
    factory_class = factories.Entry
    user_factory = factories.User # this is the user that will be authenticated for testing
    update_data = {'rating': 5}

    def setUp(self):
       self.author = factories.Author.create()
       super(EntryAPITestCase, self).setUp()

    def get_object(self, factory):
        return factory.create(authors=[self.author])

    def get_create_data(self):
       return {'headline': 'Lucifer Sam',
               'body_text': 'is a song by British psychedelic rock band Pink Floyd.',
               'authors': [self.author.pk],
               'rating': 4,
               'n_pingbacks': 0,
               'n_comments': 0,
               'pub_date': datetime.date(2014, 11, 12),
               'blog': self.object.blog.pk}

And our tests pass again.