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.

Did a presentation at work today about editing PDFs with Python. Mixing and matching libraries is fast, but you’ll run into a bunch of inefficiencies that result in slow processing and huge files. I should write a book. For serious.

Always Solve for X

Reflecting back on the products I’ve released over the years I noticed a key difference between the successful products and the non-successful products. The difference wasn’t in market or craftsmanship. The difference started before I wrote a single line of code.

With ImageXY we were focused on designing a solution to real estate agents uploading photos that were the wrong size or too large into a crm. When building it we concentrated on that single use-case and kept our solution focused. The result was a batch image resizing app that didn’t have all the complexities of its competitors. And it turned out a lot of non-real estate agents also had the same problem and our solution worked for them as well.

Other products, like Byoyomi, a timer application that let you save and run multiple timers on your Mac, were flops. The application was stable and worked as advertised, but it was more of a solution in search of a problem.

As developers its easy to dive straight into writing code or designing interfaces the moment we have an idea that we think might work. And as a developer taking those random ideas and turning them into a reality is fun. But the chances of those inspiration-turned-hacking sessions turning into a successful product are slim.

Instead of diving into code the moment we have an idea, it’s critical that you do the hard work before your hacking sessions. The work that isn’t necessarily fun and feels like work. Take a step-back form Sketch and define, with words, the problem you’re trying to solve.

If you can’t clearly articulate the problem and who’s having that problem, any solution you build will be equally muddled.

When designing anything, you first need to define the problem that you’re trying to solve.

The major difference between my successful products and those that were just that. The successful products had a clear problem that they were solving for a specific niche while the unsuccessful ones did not. It’s much easier to solve for X when you’ve looked at the rest of the equation.

Listening to the Rework Podcast and @mijustin’s idea of wanting to build calm technology company with @transistorFM completely resonates with me. His interview starts halfway-ish through.overcast.fm

More great things coming out of Helsinki (besides my favorite Marimekko, iitara, Aalto, and Linux). Maybe not as high up on the list, but Yotto is pretty great.

Riding the train in to Shinjuku and saw a mom and her son waving goodbye to the train from the balcony of their mansion as we whizzed on by. Such a wholesome routine.

Autumn Festival at the shrine near our house. It started raining so we left before the mikoshi arrived. 😀🍂🍁

Checked in at 泉区地域子育て支援拠点 すきっぷ. Went to a local shi-en center to let Leo play around. This one is great because you can see trains from inside. Brilliant!

Twitter’s Sticking Power is FOMO

I’ve spent the last week preparing and executing a move from Fujisawa to our new house in Yokohama. And in that week I’ve naturally taken a break from the twitter outrage machine. It’s been such a breath of fresh air. I’m tempted to delete the app entirely. Fear of missing out on potential professional developments, as it’s used by a large percentage of developers, is probably the only reason I haven’t done so.

Introduced ourselves to our new neighbors across the street at our new house. They’re super nice and have a granddaughter around Leo’s age. Such a relief.

Apple Wallet + FaceID = 😍

It took getting a mortgage for me to get a credit card in Japan. The registration process was polished using the camera to read the card details and Messages to auto fill the verification code sent via SMS. Paying for things just using FaceID is so fluid. Not to mention the privacy and security benefits of using a dynamic card number. Hats off to the designers and engineers behind it all. Well done!

Apple needs to contract Johnny Ive’s new company to do voice overs for the product videos. I miss the accent.