Testing Absinthe with ExUnit
This is how I tested some GraphQL endpoints using ExUnit
I’ve been trying to learn more Elixir recently and I’ve had reason to be looking at Absinthe, which is used to get GraphQL functionality in Elixir.
Something I couldn’t find a definitive best practice on was how best to test the various endpoints you end up creating for GraphQL to consume.
This article is my stab at writing some Absinthe tests. The huge caveat being that I am still very much learning all technologies involved. If you have any opinions on how the following could be improved please do get in touch.
How GraphQL likes to receive query data
In a RESTful interface you could do something like
And expect to get back some data about a Note
with an ID of 1.
With GraphQL you would send something like
This is pretty awesome, because instead of knowing you will get back something
about a Note
now we are explicitly stating we want the title
and the
author_id
.
How that looks like when we make a web request is something like this
Pretty ugly, but you will notice there is some extra information in this
request, our query is wrapped in a query
attribute and there are two other
attributes in this object; variables
and operationName
.
This is important because when we’re testing our endpoints we will need to make sure we send the same type of data.
The result of this call will look like this
How GraphQL likes to receive mutation data
In GraphQL when we want to create or update something we do what it calls a mutation.
This looks something like
This will create a Note
with the title “test” and with the object it creates
return the title attribute to me.
Again this is really nice because often when we create an object we only care about a small subset of the information used to created in our application.
The web request for the above mutation looks like this
Here we see again those three attributes; query
, variables
, and operationName
.
Lets test Absinthe
Okay, enough preamble, lets test something!
I will share the three relevant files and under each I will walk through what I’ve included and done and why.
- I want to exercise our Note resolver, so I’ve created a
NoteResolverTest
module. - I
use MyApp.ConnCase
which sets up theCaseTemplate
I want to use for these tests. This sets up some connection information, more on this shortly. - I
alias MyApp.Notes
because I need access to theNotes
module later. - I
alias MyApp.AbsintheHelpers
to get access to some helpers I have written, more on this shortly - Setting the
@note
map is just a convenience for later in my test file. - I set up a describe block for “Note Resolver”, this is where I will put my tests.
- We have one test in here, rather poorly named “find/2 returns a note”, we pass in a context which is set in my
CaseTemplate
. - The
{:ok, note} = Notes.create_note(@note)
line just creates a note in our system, howcreate_note/1
works is unimportant for this article. - We then construct our
query
as a string, note we interpolate in thenote.id
that we care about. - Next we take our connection and perform a
post
to our endpoint (in this case/graphiql
) and we pass in the result ofAbsintheHelpers.query_skeleton/2
, more on this shortly. - Now we’ve made the call we can assert based on the response, we dig into the
json_response
into “data”, and “note” to get to “id” which we then compare to ournote.id
which we cast to a String.
At a high level I think this makes sense, we set up our database, we construct our query, we execute and then we compare results.
Lets look now at the relevant parts of MyApp.ConnCase
, which sets the connection we use to make our post request.
- We do this inside a
setup
block so that it runs before each of our tests. - In this setup our requests need authenticated, so we
sign_up
a user and store their authenticationtoken
, how this happens isn’t important for this article. - We then build a connection and set two headers, the first is our
authorization
header, that sets our token. The second iscontent-type
which specifies that it will be sending JSON. - Finally, we return
{:ok, %{conn: conn}}
so that we can have access to the connection from our tests.
Finally, lets look at our AbsintheHelpers
- I wanted to abstract this out into its own module so I could use them in several tests, so I made
AbsintheHelpers
, this name could be improved. query_skeleton/2
accepts two parameters, our query and a name we want to give our query.- This function returns a map just like we saw how GraphQL likes to receive data.
You will note that when setting the query
attribute I’ve started the string with the word “query”, this wasn’t in our earlier example but tests fail when I don’t do this, I’m not sure why. If you know please do comment!
Testing Mutations
Mutation testing work almost the exact same, you construct your mutation and send it and then test for what gets returned or set in the database. The big difference is unlike with queries were things broke if I didn’t start the string with the word “query”, mutations need to not have that.
This leads me to my second function in AbsintheHelpers
This simple function generates a map with just the query and no additional text. An example query would be
Thanks
The only bit of information I could find on this was a post over on the Elixir Forum, without which I would have stayed lost for a long time.
Thanks for reading and hopefully this was helpful.