'How to Test and Debug a GraphQL API in Scala' post illustration

How to Test and Debug a GraphQL API in Scala

avatar

How do you test and debug Scala applications that use GraphQL?

It's very easy to start, and in this article we show you how. We also show the interactions between the application components starting from receiving the GraphQL queries until sending responses to the client application.

By the end of our guide, you'll feel comfortable with testing and debugging GraphQL APIs in Scala.

Before we show the tests, let's first have a look at the Scala project.

Scala, Sangria, and GraphQL API

The application we test and debug is based on Scala 2.12, Play Framework 2.7, and Sangria.

A complete guideline on how to create a GraphQL API server in Scala you can find here.

To write tests, we opted for the default Play's utilities. And since our GraphQL server will send JSON, we'll need to transform it to valid Scala objects. Hence, we also need to install spray-json.

Here are all the dependencies we registered in build.sbt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
libraryDependencies ++= Seq(
  evolutions,
  guice,
  "org.sangria-graphql" %% "sangria-play-json" % "1.0.5",
  "org.sangria-graphql" %% "sangria" % "1.4.2",

  "com.h2database" % "h2" % "1.4.197",

  "com.typesafe.play" %% "play-slick" % "4.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "4.0.0",
  "com.typesafe.slick" %% "slick" % "3.3.0",
  "com.typesafe.slick" %% "slick-hikaricp" % "3.3.0",

  "org.scalatestplus.play" %% "scalatestplus-play" % "4.0.1" % Test,
  "io.spray" %% "spray-json" % "1.3.5"
)

The repository with the code is located here: GraphQL API example with Tests. Our application uses the built-in mechanism of evolutions in Play Framework that allows us to automate creating and modifying the database schema. Specifically, it is used to add three Post object into the in-memory instance of the H2 database.

Below is the project structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
scala-graphql-api
├── app                        # The Scala app source code
    ├── controllers            # Contains controllers to executes GraphQL queries
    ├── errors                 # Various error classes
    ├── graphql                # GraphQL schemas, resolvers, and global GraphQL objects
    ├── models                 # The application models
    ├── modules                # The modules such as PostModule and DBModule
    ├── repositories           # Contains the trait PostRepository
    ├── services               # Contains app services
    └── views                  # Layouts such as GraphiQL layout used for debugging
├── conf
├── project
├── public
├── test
    └── specs                  # The Scala and GraphQL specifications
        ├── GraphiQLRouteSpec  # Spec that tests the GraphiQL route
        ├── PostSpec           # Spec to test the Post entity
        ├── PreparedInput      # Mocks of GraphQL queries and mutations for testing
        └── TestHelper         # A trait to be extended by spec classes
├── .gitignore
├── .travis.yml
├── build.sbt
└── README.md

What do we have in the test/specs directory? First, let's explain what TestHelper and PreparedInput are. TestHelper is a trait that imports the basic functionality to be used by all concrete test classes. PreparedInput is the data — GraphQL queries and mutations — that we'll feed into Play's FakeRequest for testing. We keep them in a separate trait to improve the code legibility.

There are also two classes that contain the test cases. GraphiQLRouteSpec verifies that our AppController responds with the GraphiQL HTML template. This will be used for debugging later in this article. PostSpec contains the tests for our Post entity.

In the next section, we discuss the TestHelper trait. After that, we'll focus on how a test is created.

Preparing for testing GraphQL in Scala with TestHelper

Let's create a trait TestHelper that imports a few key dependencies required for all test classes in our Scala app. Each concrete test class will extend TestHelper to avoid duplicating dependencies.

Here are the dependencies that test/specs/TestHelper.scala imports:

  • PlaySpec, a base class for tests for Play Framework applications.
  • GuiceOneAppPerSuite, a trait that provides a new instance of Application for ScalaTest Suite.
  • Injecting, makes possible to inject components under test into test classes.
  • PreparedInput, a trait that contains the variables with the entry data.

The TestHelper trait looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
  * Prepares tools for easy writing and execution of tests.
  * Injecting dependencies, filling the database with initial data, defines timeouts for requests.
  */
trait TestHelper extends PlaySpec with GuiceOneAppPerSuite with Injecting with PreparedInput {

  /**
    * Implicit definition the execution context for asynchronous operations.
    */
  implicit lazy val executionContext: ExecutionContext = inject[ExecutionContext]

  /**
    * Creates Akka Materializer for this application.
    */
  implicit lazy val materializer: Materializer = app.materializer

  /**
    * Defines timeouts for http requests.
    */
  implicit lazy val timeout: Timeout = Timeout(20, TimeUnit.SECONDS)

  /**
    * Injects instance of AppController.
    */
  lazy val appController: AppController = inject[AppController]
}

Writing a GraphQL test in Scala

Let's now focus on testing the code for the Post entity. For that, we create a class PostSpec, which extends TestHelper. PostSpec will hold the test cases that verify the successful and unsuccessful scenarios when the query or mutation is handled by the controller (the AppController in this case) for the entity Post.

A typical test looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class PostSpec extends TestHelper {

  /**
    * Imports "implicit" for conversion Post in JSON format to Post object.
    */
  import models.PostJsonProtocol._

  /**
    * Contains test specs for queries.
    */
  "Post queries" must {

    "find a post by id" in {
      val request = FakeRequest(POST, "/graphql")
                      .withHeaders(("Content-Type", "application/json"))
                      .withBody(findPost)

      val result = contentAsString(appController.graphqlBody.apply(request))

      result must include("data")
      result mustNot include("errors")

      val foundPost = result.parseJson.asJsObject.fields("data")
        .asJsObject.fields("findPost").asJsObject.convertTo[Post]

      foundPost.id mustBe defined
      foundPost.id.get mustEqual 1
      foundPost.title mustEqual "Post #[1]"
      foundPost.content mustEqual "First post content"
    }
  }
}

Notice that we first imported models.PostJsonProtocol._; it contains implicit that will greatly simplify our work with queries and mutations and will let us convert the incoming JSON responses from the API server back to Scala objects. Remember that implicit is available in app/models/Post.scala. As for the code that converts JSON to Scala objects, look for the PostJsonProtocol implementation in Post.scala.

The test above verifies that the query is successfully parsed, the correct resolver is called, and resolver returns the correct data.

Here are a few more details how this test was created:

  • We specify to which test suite the test refers: "Post queries" must { /* test */ }.
  • We give a name for the test. The name should clearly specify what the test verifies: "finds a post by ID" in { /* test */}.
  • Using the Play's object FakeRequest, we imitate sending an HTTP request to our Scala API:
1
2
3
val request = FakeRequest(POST, "/graphql")
                .withHeaders(("Content-Type", "application/json"))
                .withBody(findPost)

Pay attention to the variable findPost. This is JsValue that contains the GraphQL query or mutation that are sent to the Scala server. All these variables you can find in the trait PreparedInput (this file is located next to PostSpec and TestHelper).

  • We finally send a request:
1
val result = contentAsString(appController.graphqlBody.apply(request))

Next, we perform a preliminary validation of a string that we received in response. The Scala server may return a JSON response with two fields — data and errors. If errors weren’t produced when a query was parsed or executed, the errors field won't be presented in the JSON response.

1
2
result must include("data")
result mustNot include("errors")

In the response object, we expected at least one instance of the Post entity. We just parse the received response and convert it to a Post object:

1
2
3
val foundPost = result.parseJson.asJsObject.fields("data")
                                .asJsObject.fields("findPost")
                                .asJsObject.convertTo[Post]

After that, we verify that the Post instance we received has the correct data:

1
2
3
4
foundPost.id mustBe defined
foundPost.id.get mustEqual 1
foundPost.title mustEqual "Fist post"
foundPost.content mustEqual "First content"

You can find a few more tests in PostSpec.scala, and all of them are created the same way.

Now, just clone the project and run the tests from the root directory. First, run sbt, and when you are in the SBT console, run the tests:

  • Run all tests using the test command,
  • Run a separate spec with the command testOnly *PostSpec, or
  • Run a separate test case with the command testOnly *PostSpec -- -z addPost.

If the tests are successfully executed (at least, they should), you can see an output similar to the one below in your console:

Scala and GraphQL API testing – a successfully tested GraphQL query

Basic testing Scala and GraphQL is done. We can focus on how to debug our Scala API.

Debugging a Scala and GraphQL API

You can debug a Scala API using any HTTP client such as Postman, Insomnia, and others. We recommend a popular client Insomnia as it supports testing GraphQL queries and mutations and can show you various errors.

There's also a safe default — the browser IDE GraphiQL. In the official GraphiQL repository, you can find ready-to-use implementations of GraphiQL and use them in your Scala application.

To demonstrate how debugging is done, we use GraphiQL for its simplicity.

In our Scala application, we added the template graphiql.scala.html under app/views. To be able to debug with GraphiQL, our application has a controller method to serve this template:

1
def graphiql = Action(Ok(views.html.graphiql()))

Also, we added a respective route to conf/routes:

1
2
# Route for in-browser IDE GraphiQL
GET         /                    controllers.AppController.graphiql

Run the application using the command sbt run and open your browser at http://localhost:9000. (If necessary, apply the script to migrate the in-memory H2 database instance.)

GraphiQL – a Graphical interface to debug GraphQL-based applications

As you can see in the screenshot, the debugger workspace is divided into four areas:

  1. First goes the field where you can write queries and mutations.
  2. Second is the field to add and configure variables for queries (optional).
  3. Third is the area where you see the returned JSON responses from the server.
  4. Fourth is the tab Docs where you can view the entire GraphQL schema for your API.

Let's have a closer look at Docs (shown in the screenshot below).

The Docs tab in the GraphiQL interface

You can navigate to a concrete schema query or mutation.

Below is the list of available mutations in our sample Scala app. Notice that the obligatory variables are have the exclamation point "!" after them:

The Docs tab with a mutation in the GraphiQL interface

To debug a GraphQL server, you can first verify if your schema looks fine. Do the queries and mutations look right? Are the parameters passed to them correct?

If you're satisfied with how the schema looks, you can start debugging by sending API requests. The request below attempts to add a new post:

1
2
3
4
5
6
7
mutation {
  addPost(title:"Some Post", content:"First post") {
    id
    title
    content
  }
}

In response, a JSON object is returned. Here's what it looks like if the request was built correctly:

1
2
3
4
5
6
7
8
9
{
  "data": {
    "addPost": {
      "id": 1,
      "title": "Some Post",
      "content": "First post"
    }
  }
}

And this is what you get in return from our Scala server if there's an error in a GraphQL query. For example, if you send an addPos query instead of addPost, you get a default error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "data": null,
  "errors": [
    {
      "message": "Cannot query field 'addPos' on type 'Mutation'. Did you mean 'addPost'? (line 2, column 2):\n addPos(\n ^",
      "locations": [
        {
          "line": 2,
          "column": 2
        }
      ]
    }
  ]
}

If you'd like to know how to handle such errors in a GraphQL and Scala API, you can read a dedicated article on error handling.

As you can see, a default GraphiQL IDE is good enough to debug simple cases. If you want a bit more functionality, try out Insomnia or any other similar tool.


Using the simple tools such as the default GraphiQL IDE and PlaySpec, you can debug and test your GraphQL and Scala API. And if your integration tests are written properly, you'll always be sure that the errors will always expose themselves.

If you're looking for a developer or considering starting a new project,
we are always ready to help!