Overview

For testing our API I’m going to use Snapshot testing method; if you are not familiar with snapshot testing, it is a way to save a state of our application, e.g. an endpoint response and in our test case check that endpoint still returns that response.

To test our API we create a client, request to the endpoint and get the response then check it. We’ll Cover some cases you may not find easy to test.

Our App

I’m going to use the hackernews clone app using Graphene that I built using this tutorial I mentioned earlier and we write tests for it, also we’ll implement just one more thing just to cover a different case of testing.

Testing

We said we’re going to use snapshot testing To snapshot test we use library snapshottest for python; this library helps us in capturing endpoints response and check it again when we run the tests. for the next step, we need a client to execute GraphQL requests, hopefully, it’s done by Graphene itself.

Now we have everything we needed we can write a helper class for our tests. Create a tests folder under links app; now we are going to create a APITestCase class with some useful methods to test our API:

utils/test_utils.py

There are two key parts here: first is that we initialized a client for executing our graphql requests with our app schema(line 12), second is that we generate context using RequestFactory and setting user which is requesting and files(if we have any) for it.

you can read about this process here.

Snapshot testing CreateLink

Now we can write a test for our CreateLink mutation; first, we need to know what our request is going to be. in this case our mutation will look like this:

mutation CreateLink($url: String!, $desc: String!){

createLink(description: $desc, url: $url){

id,

url,

description

}

}

with variables:

{

"url": "example.com",

"desc": "example link"

}

And there are two possible test cases for this mutation; first is when a user is logged in(we return the created link) second is when the user is not logged in (we raise an exception) lets start with case one, create a tests folder in links app and a test_schema.py file in it.

First case

using our test_utils we can come up with this:

I saved mutation we wrote earlier in graphql_requests file to keep the test file clean, basically we are just giving the request, variables and context to snapshot_graphql_request function and checking its results(how?). This magic is done by snapshottest; when you run the tests for the first time you can see there is a folder beside your test_schema.py named snapshots and the response of your test is stored there so anytime you run the tests it checks the output with the saved file, just make sure the output is what you really want.

Second case

now you want to test the CreateLink when user is not logged in, seems easy right? it’s just like our previous test case except that it does not contain user object.

def test_create_link_user_anonymous(self):

self.snapshot_graphql_request(

request_string=CREATE_LINK_MUTATION,

variables={"url": "example.com", "desc": "example link"},

)

Now when you run tests you see an error:

graphql.error.located_error.GraphQLLocatedError: You must be logged to create link!

Do not panic! in the next line, you can see that your tests are passed.

..

Ran 2 tests in 0.006s

So what is the problem?

Actually there is no problem here, it is part of the GraphQL to catch errors and return data as much as it can. if you take a look at snap_test_schema.py file you see this:

snapshots['LinkTestCase::test_create_link_user_anonymous 1'] = {

'data': {

'createLink': None

},

'errors': [

{

'locations': [

{

'column': 3,

'line': 3

}

],

'message': 'You must be logged to create link!',

'path': [

'createLink'

]

}

]

}

GraphQL catch the errors and put them inside errors attribute so that it can deliver other data that was sent by the server. you can read more about this behavior here.

Tip: If you found out your snapshot result is not what you wanted you can reproduce snapshot for a test case using — snapshot-update argument with your test command.

Other cases are similar to this one we wrote so I’m sure you can write them yourself.

Check Specific Values from response

Checking our API response values is fairly simple we execute the request and check result has a specific attribute or value, this can be useful if you want to test an endpoint that is not returning same response every time and cannot be snapshotted, to get the values from the response you can get data attribute from GraphQL response and check it.

Test uploading files

This is an additional case which we don’t have here but it can be tricky so I explain it; imagine your model had a picture filed and you wanted to test it, here is what you need to do for this.

Add a file variable to your context in the test case put files under context in GraphQL request( this gets done by test utils) process the request and access files in mutation

First, read this page to implement a mutation that accepts files.

Then add files key to your context dictionary in the test case:

context={

"user": self.dummy_user,

"files": {

"picture": SimpleUploadedFile(

"img1-link.jpeg", content=None,

content_type="image/jpeg"),

}

}

Now in the mutation, you can access file from info.context.FILES dict with picture key.

Summary

With the importance of testing in developing applications, it’s important to know how to write efficient test cases, and here we learned about snapshot testing which can reduce the difficulty to test GraphQL endpoints if used correctly.

I learned most of this from Graphene github discussions so it’s a good idea to check that for more information when you encounter a problem.