In this lesson, we're diving into how to effectively test your GraphQL Server using RSpec. Prepare for a detailed exploration on how to run queries and mutations against the schema, making assertions on the GraphQL response, and extracting queries with variables for a dry test setup.

Understanding Your Test Environment

We'll start by taking a look at the fixtures. These live under spec/fixtures where a fixture for accounts has been created. This setup injects a test account with a password into the database automatically, perfect for running tests. To make this happen, we need to set config.global_fixtures: :all inside the rails_helper.rb file. This config ensures Rails looks for anything in the fixtures folder and loads them all.

Moving on, let's take a quick journey to the app/controllers, specifically the GraphQLController. If you remember, this was set up by the GraphQL-Ruby gem. Here, you'll see a line that reads CoinfusionSchema. Coinfusion is the name of our example app. Depending on your setup, your application name will live here. The execute function indicates where your query gets passed in.

CoinfusionSchema.execute(query)

Though there might also be variables and context, we will circle back to these later on. For now, it's important to understand this fundamental aspect – that every query hits this endpoint.

Writing Tests

We'll now head over to our first test. For our first test, namely returns a token, we'll make use of the ruby here doc syntax for multiline strings.

query = <<~GQL
  mutation {
    login (input: { 
      email: "test@example.com", 
      password: "bad" 
    }) 
    {
      token 
    }
  }
GQL

We'll replace hardcoded data with actual user credentials, and using CoinfusionSchema.execute with the query as a parameter should work just fine.

Let's test this by printing out the result:

# spec/graphql/mutations/login_spec.rb
require "rails_helper"

RSpec.describe "mutation login" do
  it "authenticates the account returning a token" do
    query = <<~GQL
      mutation {
        login (input: { 
          email: "test@example.com", 
          password: "hunter2" 
        }) 
        {
          token 
        }
      }
    GQL

    result = Coinfusion::Schema.execute(query: query)
    puts result
  end
end

The output of this should be a GraphQL query result, similar to a Hash in Ruby. Specifically, a hash containing a login field and a nested token field. This is the data we're after. Next, let's test that these fields exist and that a token is returned.

query = <<~GQL
  mutation {
    login (input: { 
      email: "test@example.com", 
      password: "hunter2" 
    }) 
    {
      token 
    }
  }
GQL

result = Coinfusion::Schema.execute(query: query)
expect(result.dig("data", "login", "token")).not_to be_blank

Nice. As this is testing for successful login, how about the inverse? Let's mimic a login failure by providing an invalid password. We should be able to test that the result is nil, and ensure the appropriate error message is returned.

it "returns an error when authentication fails" do
  query = <<~GQL
    mutation {
      login (input: { 
        email: "test@example.com", 
        password: "bad" 
      }) 
      {
        token 
      }
    }
  GQL

  result = Coinfusion::Schema.execute(query: query)
  expect(result["data"]).to be_nil
  expect(result["errors"].first["message"]).to eq("Invalid email or password")
end

Running both tests should be successful.

DRYing up the tests

We've got working tests, but there's a lot of duplication here with our GraphQL query. Let's extract this and make it a method named login_query.

Variables come in handy here for reusability. We can name our mutations and queries, and we more or less stick to whatever they are called initially. Setting up the variable names and marking them as required fields allows us to substitute them in the query instead of the hardcoded email and password values. This DRYs up the code and compacts our login query from multiple lines in every test to a singular method call with the necessary data parsed in.

def login_query
  <<~GQL
    mutation Login($email: String!, $password: String!) {
      login(
        email: $email
        password: $password
      ) {
        token
      }
    }
  GQL
end

And, with our tested data now stored in variables, we can pass it to the login_query inside each test.

it "authenticates the account returning a token" do
  result = CoinfusionSchema.execute(login_query, variables: {
    email: "test@example.com",
    password: "hunter2"
  })
  expect(result.dig("data", "login", "token")).not_to be_blank
end

it "returns an error when authentication fails" do
  result = CoinfusionSchema.execute(login_query, variables: {
    email: "test@example.com",
    password: "bad"
  })
  expect(result["data"]).to be_nil
  expect(result["errors"].first["message"]).to eq("Invalid email or password")
end

Rerunning these tests should lead to the same results as before.

Writing Registration Tests

Finally, we're going to port the above logic to our register_spec. Starting with the returns error and validation fields spec, we can construct a registration query that will be supplied for our tests. Unlike the login_query this one takes a lot more options.

def register_query
  <<~GQL
    mutation Register($accountType: AccountType!, $email: String!, $password: String!, $name: String!, $dateOfBirth: String!) {
      register(
        accountType: $accountType,
        email: $email,
        password: $password,
        name: $name,
        dateOfBirth: $dateOfBirth
      ) {
        name
        email
        token
      }
    }
  GQL
end

We can then write our first test to handle when registration fails:

it "returns an error when validation fails" do
  result = CoinfusionSchema.execute(register_query, variables: {
    accountType: "individual",
    email: "test@example.com",
    password: "",
    name: "Test",
    dateOfBirth: "1990-01-01"
  })
  expect(result["errors"].first["message"]).to eq("Register failed.")
end

For the successful registration test, we need a valid password. Following this, we can then test and compare that the registered user's name and email match the input supplied, and that a token is returned.

it "creates a new account returning a token" do
  result = CoinfusionSchema.execute(register_query, variables: {
    accountType: "individual",
    email: "test@example.com",
    password: "long-password!",
    name: "Test",
    dateOfBirth: "1990-01-01"
  })
  expect(result.dig("data", "register", "email")).to eq("test@example.com")
  expect(result.dig("data", "register", "name")).to eq("Test")
  expect(result.dig("data", "register", "token")).not_to be_blank
end

And with that, our registration tests are working.

Implementing tests for the Me query

This query retrieves information about the currently logged-in user. This is where we'll make use of the context for GraphQL, allowing us to pass in an account.

# spec/graphql/queries/me_spec.rb
require "rails_helper"

RSpec.describe "query me" do
  let(:account) { accounts(:test) }

  it "returns info about the current logged in account" do
    query = <<~GQL
      query {
        me {
          id
          name
          email
        }
      }
    GQL

    result = CoinfusionSchema.execute(query, context: { current_account: account })

    expect(result.dig("data", "me", "id")).to eq(String(account.id))
    expect(result.dig("data", "me", "name")).to eq("Test Account")
    expect(result.dig("data", "me", "email")).to eq("test@example.com")
  end
end

Running this test should be successful. Here, we pass in the current_account or any object that's relevant to your GraphQL context directly via the keys of the context param. This is a very efficient way to pass context-specific information into your tests.

To wrap up, let's run all of the GraphQL tests together and confirm that all are passing. We can do this by running bin/rspec spec/graphql and voilà! The tests are all green.

Conclusion

I hope that this lesson has been a comprehensive guide to testing your GraphQL server with RSpec. Testing is an integral part of development and being able to effectively ascertain the reliability of your GraphQL server is an invaluable skill.

Please don't hesitate to drop your comments, questions, or any topics you would like to know more about GraphQL. Happy coding!

Discussion (0)

To comment you need to sign up for a free account or sign in.

Get new videos in your inbox weekly!

Be the first to hear about new courses and videos launching. You’ll only receive relevant news, no spam.