Test your GraphQL API in Elixir
While developing a new GraphQL API, I thought about the best way to actually test GraphQL endpoints when using Phoenix. In the end, the test coverage is similar to a controller test. GraphQL APIs have a different structure compared to REST APIs. Therefore, we will build some helpers to have an easy way to test our queries and mutations.
Example Schema
For our example, we’ll assume to have the following GraphQL Schema.
# Type definition of a User
type User {
id: ID!
name: String!
}
# Query to get all users
type Query {
users: [User!]!
}
# Mutation to update a user's name
type Mutation {
updateUserName(id: ID!, name: String!): User!
}
We have a User
, a query to get all users and a mutation to update a certain users’ name.
How a GraphQL Query looks like
GraphQL APIs work by sending a POST
HTTP requests to the GraphQL endpoint (e.g. /api/graphql
) with the query
and a variables
field in theJSON payload. As a response, it returns an HTTP success status (200) with a data
or an error
field in the JSON response. Usually, when you use an GraphQL API, all of this gets abstracted by the clients like Apollo or Relay. However, for testing, it’s important to us to know the form of a request.
POST https://domain.com/api/graphql
Content-Type: application/json
{
"query": "users { name }"
}
Building the query in our tests
We now know how an HTTP request for a GraphQL query needs to look like, so we can build it in our tests. In this example, we use our users
query, to build a GraphQL query in our test suite. Since we don’t need any variables for now, we just need to add the query
field to our payload.
test "returns all users" do
build_conn()
|> post("api/graphql", %{
"query" => """
users {
id
name
}
"""
})
end
However, we can easily extract this logic to a GraphQL test helper.
Create test helpers
Because most of the time I just want to create a GraphQL query and immediately send it, my idea for the API looked like this:
build_conn() |> graphql_query(query: @query) # => Returns result
So I built a GraphQLHelper
to do so:
# test/support/graphql_helper.ex
defmodule MyApp.GraphqlHelper do
use Phoenix.ConnTest
# We need to set the default endpoint for ConnTest
@endpoint MyApp.Web.Endpoint
def graphql_query(conn, options) do
conn
|> post(
"/api/graphql",
build_query(options[:query], options[:variables])
)
|> json_response(200)
end
defp build_query(query, variables) do
%{
"query" => query,
"variables" => variables
}
end
end
To make this helper accessible per default in your tests you need to import it in ConnCase
.
# test/support/conn_case.ex
defmodule MyApp.DataCase
# ...
using do
# ...
import MyApp.GraphqlHelper
end
Authentication
Most of the time, your GraphQL API needs a Token for authenticating a user. So in our example, we use a token-based authentication in our API and also create a AuthenticationHelper
that will create a new user session and adds the token to the HTTP Header.
defmodule MyApp.AuthenticationHelper do
def authenticate_user(conn, user) do
token = MyApp.Accounts.create_user_session(user).token
conn
|> Plug.Conn.put_req_header("authorization", "Session #{token}")
end
end
We also import this helper in our ConnCase
and now can test our GraphQL API.
Building a mutation
With all those helpers, we now can easily test our GraphQL API. Here is an example how to update the name of a user. This is a mutation that requires the user ID and their new name.
defmodule MyApp.UserResolverTest do
use DocFlow.Web.ConnCase
setup do
user = Fixtures.create(:user, name: "Jon Snow")
{:ok, user}
end
@query """
mutation UpdateUserName($id: ID!, $name: String!) {
updateUserName(id: $id, name: $name) {
name
}
}
"""
test "Updates the name of the bastard", %{user: user} do
%{"data": %{"updateUsername": %{"name": name}}} =
conn
|> authenticate_user(user)
|> graphql_query(
query: @query,
variables: %{
id: user.id,
name: "Aegon Tagaryen",
}
)
assert name == "Aegon Targaryen"
end
end