Unit Testing django-graphene GraphQL endpoints with pytest

On a project at work I’ve been learning GraphQL. I’m in charge of both developing the backend ( using the wonderful graphene-django package) and the frontend ( using Typescript / Vue.js / axios ) for this specific feature.

After wrapping my head around GraphQL and getting a basic endpoint working I wanted to write some tests to ensure that filtering and other requirements are working as expected.

For a proper End-to-end test I’d usually setup a client and post a json dictionary or such. With End-to-end GraphQL tests you need to send actual GraphQL queries, which makes sense but it feels a bit like SQL Injection.

Below is how I’ve settled organizing and writing my GraphQL tests.

tests/
├── __init__.py
├── conftest.py
├── test_myapp/
│   └── test_schema.py

Because the graphql client will be reused across the django site, I added a global fixture that will automatically load mty project’s schema.

# tests/conftest.py
@pytest.fixture
def graphql_client():
    from myproject.schema import schema
    from graphene.test import Client
    return Client(schema)

In this example I’m testing that I’m filtering data as expected when passing a search parameter.

For setup, first I write a query as its own fixture so I can re-use it throughout the test and it’s clear exactly what is going to be run. Second, I make sure the query uses variables instead of hard-coded values when querying so I can change the input depending on the test. Third, setup a model_bakery fixture for data that I expect to find.

import pytest
from model_bakery import baker

@pytest.mark.django_db
class TestMyModelData:
    @pytest.fixture
    def query(self):
        return """
query testGetMyModel($searchParam: String!){
  myModelData(searchParam: $searchParam) {
    totalCount
  }
}"""

    @pytest.fixture
    def my_model(self):
        baker.make(
            "myapp.MyModel",
            total_count="20", # Decimal field    
            )

    def test_none_response(self, graphql_client, query, my_model):
        executed = graphql_client.execute(query, variables={"searchParam": "skittles"})
        assert executed == {"data": {"myModelData": None}}

    def test_filters_usage(self, graphql_client, query, my_model):
        params = {"searchParam": "skittles"}
        executed = graphql_client.execute(query, variables=params)
        assert executed == {
            "data": {
                "myModelData": {
                    "totalCount": 20
                }
            }
        }

Executing each test I simply pass my query and required variables for the test/query. In this I’m testing the same query twice: once with and one without a searchParameter. My expectation is that I get no results without a search term and data when to my graphql_client fixture.

As the return value from our client is a dictionary, I can simply assert my expecte results with the actual results. If something changes I’ll know immediately.

Using the techniques above I can easily add new tests for my GraphQL endpoint as the available changes or bugs are found.