Testing GraphQL with Graphene Django

04 Mar 2017
The missing guide

Testing old-school APIs is super fun and easy. Frameworks like Django have a huge emphasis of testing; and make it very easy to do so.

But it is 2017, and GraphQL is changing the way that we write APIs. In particular, Graphene Django is an easy to use library for writing GraphQL APIs within Django.

However, Graphene Django doesn't include a testing guide! But fear not, testing is easy, simple and clear.

Our helper class

Executing a GraphQL query is very simple - you just POST it to your endpoint and get JSON back. To make it even easer in test, we use a little helper class to abstract away the details:

import json
from django.test import TestCase
from django.test import Client


# Inherit from this in your test cases
class GraphQLTestCase(TestCase):

    def setUp(self):
        self._client = Client()

    def query(self, query: str, op_name: str = None, input: dict = None):
        '''
        Args:
            query (string) - GraphQL query to run
            op_name (string) - If the query is a mutation or named query, you must
                               supply the op_name.  For annon queries ("{ ... }"),
                               should be None (default).
            input (dict) - If provided, the $input variable in GraphQL will be set
                           to this value

        Returns:
            dict, response from graphql endpoint.  The response has the "data" key.
                  It will have the "error" key if any error happened.
        '''
        body = {'query': query}
        if op_name:
            body['operation_name'] = op_name
        if input:
            body['variables'] = {'input': input}

        resp = self._client.post('/graphql', json.dumps(body),
                                 content_type='application/json')
        jresp = json.loads(resp.content.decode())
        return jresp

    def assertResponseNoErrors(self, resp: dict, expected: dict):
        '''
        Assert that the resp (as retuened from query) has the data from
        expected
        '''
        self.assertNotIn('errors', resp, 'Response had errors')
        self.assertEqual(resp['data'], expected, 'Response has correct data')

Then you just test! You can write the same style tests as if you were using the excellent Django Rest Framework, but with GraphQL. Here's a basic example of a query:

    def test_is_logged_in(self):
        resp = self.query('{ isLoggedIn }')
        self.assertResponseNoErrors(resp, {'isLoggedIn': False})

Or a more complex example with a mutation:

    def test_login_mutation_successful(self):
        User.objects.create(username='test', password='hunter2')
        resp = self.query(
            # The mutation's graphql code
            '''
            mutation logIn($input: LogInInput!) {
                logIn(input: $input) {
                    success
                }
            }
            ''',
            # The operation name (from the 1st line of the mutation)
            op_name='logIn',
            input={'username': 'test', 'password': 'hunter2'}
        )
        self.assertResponseNoErrors(resp, {'logIn': {'success': True}})

A GraphQL future

At LearntEmail, we're really excited to be using GraphQL for our API. Testing is so important stable software - so good testing tools are a must.

Feel free to tweet to us @LearntEmail with your thoughts on GraphQL testing, and subscribe (below) to follow our GraphQL journey & experiences.