Test these views

Soo… Where I work, (As a Back-end Developer (Soon to be Full stack)), We’ve been working on changing the site architecture from a monolith (hope i spelt that right…im too lazy to google it) to a Microservice-y architecture.

Basically, these means that we’ll be breaking down the site into services that communicate with eachother via APIs.(Its mainly Django bdw) Now I’m working on this service, trying to replace the functionality thats in the current code base in my own microservice. Before today, this was my REST view using Django rest framework (DRF)

NB: If you haven’t heard of Django before, the rest of these stuff will be to you what PHP is to me


#A Simple FBV (function based view)
@api_view(http_method_names=['GET'])
def single_category(request, slug):

    #This shii is in the models (does some DB manipulation Yada yada)
    data = cached_category_skills(request.user, slug)

    return Response(data=data, status=200)


#and my CBV (class based view)

class Categories(generics.ListAPIView):
    model = Category
    serializer_class = CategorySerializer
    queryset = model.objects.all()

Straight forward yh? Its all good. Then I’ll hook ‘em up to the urls like this

    url(r'^skill/categories/$', view=skill_views.Categories.as_view(), name="categories"),
    url(r'^skill/categories/(?P<slug>[\w.@+-]+)', view=skill_views.single_category, name="category"),

This works… but little did I know that there was a simpler way to do this using DRF Introducing….Router and Viewsets

A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create().

A Router is just a quick and consistent way of wiring your view logic to a set of URLs.


class CategoriesViewSet(viewsets.ModelViewSet):
    serializer_class = CategorySerializer
    lookup_field = "slug"

    def get_queryset(self):
        return Category.objects.all()

    def retrieve(self, request, **kwargs):

        instance = self.get_object()

        serializer = SkillSerializer(instance.skill_set.all(), many=True)
        return Response(serializer.data)


router = routers.DefaultRouter()

router.register(prefix=r'categories', viewset=CategoriesViewSet,
                base_name="fetch_categories")

I’m not the official documentation so don’t expect me to gist you on what exactly this does

Then in my urls.py

 url(r'^', include(views.router.urls)),
 #Dassal

A lotta abstraction is done by the ModelViewSet class. Check this out.

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

Just Imagine the possibilities. Read the docs you’ll love it too. A lotta stuff that i woulda done manually has already been done by the ModelViewSet class. Including Browsable API Ish

Browsable API

Now to the testing…..

Damn… The funniest thing about unit tests in django is that the name of the test function must start with test if not the test wouldn’t run e.g


class BaseTestCase(TestCase):

    def test_price_of_pure_water_will_return_to_five_naira(self):
        pass

This stupid thing kept me up one night. Bdw I use django-test-plus for testing, cool library, you should check it out. It provides a lot of method that the default django test doesn’t.

So apparently, I learnt from the CTO (cool guy) today that your test for the views should also test the actual expected result, so here’s a sample of one of my unit tests

def test_quiz_questions_are_fecthed_for_quiz(self):
        response = self.client.get(
            reverse('quiz:fetch_questions-detail', kwargs={'url': self.skill1.quiz.url}))

        data = response.json()
        self.response_200(response)

        self.assertEqual(len(data), 1)

        self.assertEqual(data, [{'answer_set': [{'content': '', 'correct': False, 'id': 168}, {'content': '', 'correct': False, 'id': 167}, {
                         'content': '', 'correct': True, 'id': 166}, {'content': '', 'correct': False, 'id': 165}], 'content': 'whats my name?', 'id': 0}])

I couldn’t be more explicit ;)

I usually first test for a 200 response before I test the data… Im just used to It. The self.client.get comes from django-test-plus

Finally, there’s also this cool library django-extensions check it on github, I’ve been using it for a while, but didnt know about the management commands including:

$ python manage.py shell_plus

and

$ python manage.py show_urls

Damn good stuff

shell_plus gives some kinda Ipython interface while show_urls shows you the urls in your project and the views the point to

here are a list of other stuff that exist

[django_extensions]
    admin_generator
    clean_pyc
    clear_cache
    compile_pyc
    create_app
    create_command
    create_jobs
    create_template_tags
    delete_squashed_migrations
    describe_form
    drop_test_database
    dumpscript
    export_emails
    find_template
    generate_secret_key
    graph_models
    mail_debug
    notes
    passwd
    pipchecker
    print_settings
    print_user_for_session
    reset_db
    runjob
    runjobs
    runprofileserver
    runscript
    runserver_plus
    set_default_site
    set_fake_emails
    set_fake_passwords
    shell_plus
    show_template_tags
    show_templatetags
    show_urls
    sqlcreate
    sqldiff
    sqldsn
    sync_s3
    syncdata
    unreferenced_files
    update_permissions
    validate_templates

Really cool stuff…

Now I’m off to more debugging.

PS: check out this Wakatime extension. It tracks the amount of time you spend coding on your text editor.

comments powered by Disqus