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