Testing gRPC

The tests in the Hello World example illustrates use of the ScalaTest framework. The test coverage is not complete. It only shows how to get started with testing gRPC services. You could add to it as an exercise to increase your own knowledge.

Let’s look at the test class definition in the GreeterSpec.scala source file:

package com.example.helloworld

import scala.concurrent.Await

import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
import scala.concurrent.duration._
import scala.language.postfixOps

import akka.actor.ActorSystem
import akka.grpc.GrpcClientSettings
import akka.stream.ActorMaterializer
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.Span

class GreeterSpec
  extends Matchers
  with WordSpecLike
  with BeforeAndAfterAll
  with ScalaFutures {

  implicit val patience = PatienceConfig(5.seconds, Span(100, org.scalatest.time.Millis))

  val serverSystem: ActorSystem = {
    // important to enable HTTP/2 in server ActorSystem's config
    val conf = ConfigFactory.parseString("akka.http.server.preview.enable-http2 = on")
      .withFallback(ConfigFactory.defaultApplication())
    val sys = ActorSystem("HelloWorldServer", conf)
    val bound = new GreeterServer(sys).run()
    // make sure server is bound before using client
    bound.futureValue
    sys
  }

  implicit val clientSystem = ActorSystem("HelloWorldClient")
  implicit val mat = ActorMaterializer()

  val client = {
    implicit val ec = clientSystem.dispatcher
    GreeterServiceClient(GrpcClientSettings.fromConfig("helloworld.GreeterService"))
  }

  override def afterAll: Unit = {
    Await.ready(clientSystem.terminate(), 5.seconds)
    Await.ready(serverSystem.terminate(), 5.seconds)
  }

  "GreeterService" should {
    "reply to single request" in {
      val reply = client.sayHello(HelloRequest("Alice"))
      reply.futureValue should ===(HelloReply("Hello, Alice"))
    }
  }
}

Note how we create two ActorSystems, one for the server and another for the client. The test is then using the client to verify that it retrieves the expected responses from the server.

Unit testing

The above test example is a full integration test using real client and server, including communication via HTTP/2. For some testing of the service implementation it might be more appropriate to write unit tests without interaction via the gRPC client. Since the service interface and implementation doesn’t require any gRPC intrastructure it can be tested without binding it to a HTTP server.

package com.example.helloworld

import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.postfixOps

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import org.scalatest.BeforeAndAfterAll
import org.scalatest.Matchers
import org.scalatest.WordSpecLike
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.Span

class GreeterServiceImplSpec
  extends Matchers
  with WordSpecLike
  with BeforeAndAfterAll
  with ScalaFutures {

  implicit val patience = PatienceConfig(5.seconds, Span(100, org.scalatest.time.Millis))

  val system = ActorSystem("HelloWorldServer")
  implicit val mat = ActorMaterializer.create(system)
  val service = new GreeterServiceImpl(mat)

  override def afterAll: Unit = {
    Await.ready(system.terminate(), 5.seconds)
  }

  "GreeterServiceImpl" should {
    "reply to single request" in {
      val reply = service.sayHello(HelloRequest("Bob"))
      reply.futureValue should ===(HelloReply("Hello, Bob"))
    }
  }
}

Add streaming tests

As an exercise to increase your understanding you could add tests for the streaming call, both as integration test and unit test style.

The Akka documentation of Testing streams might be useful.