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
:
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"
)
Code language: JavaScript (javascript)
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:
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
Code language: PHP (php)
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 ofApplication
for ScalaTestSuite
.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:
/**
* 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]
}
Code language: PHP (php)
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:
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"
}
}
}
Code language: PHP (php)
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:
val request = FakeRequest(POST, "/graphql")
.withHeaders(("Content-Type", "application/json"))
.withBody(findPost)
Code language: JavaScript (javascript)
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:
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.
result must include("data")
result mustNot include("errors")
Code language: PHP (php)
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:
val foundPost = result.parseJson.asJsObject.fields("data")
.asJsObject.fields("findPost")
.asJsObject.convertTo[Post]
Code language: JavaScript (javascript)
After that, we verify that the Post instance we received has the correct data:
foundPost.id mustBe defined
foundPost.id.get mustEqual 1
foundPost.title mustEqual "Fist post"
foundPost.content mustEqual "First content"
Code language: CSS (css)
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:
${img (location: “/images/blog/inline/2019-04-21-how-to-test-and-debug-scala-graphql-api/scala-graphql-api test.png”, alt: “Scala and GraphQL API testing – a successfully tested GraphQL query”, class: “shadow”)}
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:
def graphiql = Action(Ok(views.html.graphiql()))
Also, we added a respective route to conf/routes
:
# Route for in-browser IDE GraphiQL
GET / controllers.AppController.graphiql
Code language: PHP (php)
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.)
As you can see in the screenshot, the debugger workspace is divided into four areas:
- First goes the field where you can write queries and mutations.
- Second is the field to add and configure variables for queries (optional).
- Third is the area where you see the returned JSON responses from the server.
- 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).
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:
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:
mutation {
addPost(title:"Some Post", content:"First post") {
id
title
content
}
}
Code language: JavaScript (javascript)
In response, a JSON object is returned. Here’s what it looks like if the request was built correctly:
{
"data": {
"addPost": {
"id": 1,
"title": "Some Post",
"content": "First post"
}
}
}
Code language: JSON / JSON with Comments (json)
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:
{
"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
}
]
}
]
}
Code language: JSON / JSON with Comments (json)
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.