Feature Tests

Note

If you are calling an c.t.util.Await function on a c.t.util.Future return type in a test, it is generally considered good practice to ensure that your c.t.util.Await call includes a timeout duration, e.g., c.t.util.Await#ready.

If you are familiar with Gherkin or Cucumber or other similar testing languages and frameworks, then feature testing will feel somewhat familiar. In Finatra, a feature test always consists of an app or a server under test. See the FeatureTest trait.

We highly recommend writing feature tests for your services as they provide a very good signal of whether you have correctly implemented the features of your service. If you haven’t implemented the feature correctly, it almost doesn’t matter that you have lots of unit tests.

HTTP Server

For example, to write a feature test for an HTTP server, extend the c.t.inject.server.FeatureTest trait. Then override the server definition with an instance of your EmbeddedHttpServer.

import com.twitter.finatra.http.EmbeddedHttpServer
import com.twitter.inject.server.FeatureTest

class ExampleServerFeatureTest extends FeatureTest {
  override val server = new EmbeddedHttpServer(new ExampleServer)

  test("ExampleServer#perform feature") {
      server.httpGet(
        path = "/",
        andExpect = Status.Ok)
        ...
    }
  }
}

Thrift Server

Similarly, to write a feature test for a Thrift server and create a Finagle client to it, extend the c.t.inject.server.FeatureTest trait, override the server definition with an instance of your EmbeddedThriftServer, and then create a Thrift client from the EmbeddedThriftServer.

import com.example.thriftscala.ExampleThrift
import com.twitter.conversions.time._
import com.twitter.finatra.thrift.EmbeddedThriftServer
import com.twitter.inject.server.FeatureTest
import com.twitter.util.Await

class ExampleThriftServerFeatureTest extends FeatureTest {
  override val server = new EmbeddedThriftServer(new ExampleThriftServer)

  lazy val client: ExampleThrift[Future] =
    server.thriftClient[ExampleThrift[Future]](clientId = "client123")

  test("ExampleThriftServer#return data accordingly") {
    Await.result(client.doExample("input"), 2.seconds) should equal("output")
  }
}

Client Interface Types

As mentioned in the Scrooge Finagle Integration documentation, users have three API choices for building an interface to a Finagle Thrift client — ServicePerEndpoint, ReqRepServicePerEndpoint, and MethodPerEndpoint. This is true even when creating a test Thrift client to a Thrift server.

In the example above, we create a Thrift client in the form of the higher-kinded type interface, e.g., MyService[+MM[_]]. We could choose to create a ExampleThrift.MethodPerEndpoint interface instead by changing the type parameter given to the c.t.finatra.thrift.ThriftClient#thriftClient[T] method:

lazy val client: ExampleThrift.MethodPerEndpoint =
  server.thriftClient[ExampleThrift.MethodPerEndpoint](clientId = "client123")

Users can also choose to create a service-per-endpoint Thrift client interface by calling the c.t.finatra.thrift.ThriftClient#servicePerEndpoint[T] with either the ServicePerEndpoint or ReqReServicePerEndpoint type. E.g.,

lazy val client: ExampleThrift.ServicePerEndpoint =
  server.servicePerEndpoint[ExampleThrift.ServicePerEndpoint](clientId = "client123")

or

lazy val client: ExampleThrift.ReqRepServicePerEndpoint =
  server.servicePerEndpoint[ExampleThrift.ReqRepServicePerEndpoint](clientId = "client123")

Lastly, the Thrift client can also be expressed as a MethodPerEndpoint wrapping a service-per-endpoint type by using c.t.finatra.thrift.ThriftClient#methodPerEndpoint[T, U]. This would allow for applying a set of filters on the Thrift client interface before interacting with the Thrift client as a MethodPerEndpoint interface.

For example:

lazy val servicePerEndpoint: ExampleThrift.ServicePerEndpoint =
  server
    .servicePerEndpoint[ExampleThrift.ServicePerEndpoint](clientId = "client123")
    .filtered(???)

lazy val client: ExampleThrift.MethodPerEndpoint =
  server.methodPerEndpoint[
    ExampleThrift.ServicePerEndpoint,
    ExampleThrift.MethodPerEndpoint](servicePerEndpoint)

See the Communicate with a Thrift Service section for more information on Thrift clients.

Combined HTTP & Thrift Server

If you are extending both c.t.finatra.http.HttpServer and c.t.finatra.thrift.ThriftServer then you can feature test by constructing an EmbeddedHttpServer with ThriftClient, e.g.,

import com.example.thriftscala.ExampleThrift
import com.twitter.conversions.time._
import com.twitter.finatra.http.EmbeddedHttpServer
import com.twitter.finatra.thrift.ThriftClient
import com.twitter.inject.server.FeatureTest

class ExampleCombinedServerFeatureTest extends FeatureTest {
  override val server =
    new EmbeddedHttpServer(new ExampleCombinedServer) with ThriftClient

  lazy val client: ExampleThrift[Future] =
    server.thriftClient[ExampleThrift[Future]](clientId = "client123")

  "ExampleCombinedServer#perform feature") {
      server.httpGet(
        path = "/",
        andExpect = Status.Ok)
        ...
    }

   "ExampleCombinedServer#return data accordingly") {
      Await.result(client.doExample("input"), 2.seconds) should equal("output")
    }
  }
}

Caution

The server is specified as a def in the c.t.inject.server.FeatureTestMixin trait.

If you only want to start one instance of your server per test file make sure to override this def with a val.

For more advanced examples see: