Embedded Servers and Apps

Important

Please see the section on including test-jar dependencies in your project: Test Dependencies.

Finatra provides a way to run an embedded version of your service or app running locally on ephemeral ports. This allows you to run actual requests against an actual version of your server when testing.

The embedded utilities are also useful for testing and debugging your code when prototyping. If your service or API makes calls to other services, instead of mocking out or overriding those dependencies with dummy implementations you can always write a test using an Embedded version of your server which talks to real downstream services (of course you’d never want to commit a test like this to your source repository, especially if you run any type of continuous integration system). You’ll be able to run this test normally through the test runner of an IDE which would allow you to easily set breakpoints and step-through code for debugging. As opposed to needing to build and run your service locally and attach a remote debugger.

See:

../../_images/embedded.png

You’ll notice that this hierarchy generally follows the server trait hierarchy as c.t.finatra.http.HttpServer and c.t.finatra.thrift.ThriftServer extend from c.t.server.TwitterServer which extends from c.t.app.App.

Testing With Global Flags

The embedded servers and the embedded app allow for passing Twitter Util Flags to the server under test via the flags constructor argument (a map of flag name to flag value) which is meant to mimic setting flag values via the command line.

However it is not recommended that users set any GlobalFlag value in this manner when testing. In normal usage, the value of a GlobalFlag is only read once during the initialization of the JVM process. Which means that setting the value of a GlobalFlag as a normal flag on a server will set it for the JVM process started by the test harness and the value will not be able to be reset during the lifetime of the process. This also means that the value of the GlobalFlag would be wholly dependent on which test server is started first in the test suite, increasing the likelihood of non-deterministic effects.

A GlobalFlag can be scoped for the execution of an embedded server by passing in a value via the globalFlags arg of an embedded server. For example,

import com.twitter.finatra.http.EmbeddedHttpServer
import com.twitter.finagle.http.Status
import com.twitter.inject.server.FeatureTest
import scala.collection.immutable.ListMap

class ExampleServerFeatureTest extends FeatureTest {
  override val server = new EmbeddedHttpServer(
    twitterServer = new ExampleServer,
    globalFlags = ListMap(com.foo.bar.someGlobalFlag -> "a value")
  )

  test("ExampleServer#perform feature") {
    // any read of the `com.foo.bar.someGlobalFlag` value within the server will be "a value"
    server.httpGet(
      path = "/",
      andExpect = Status.Ok)

    ???
  }
}

Important

Global Flags are applied in insertion order with the first entry being applied “closest” to the startup of the underlying TwitterServer. Thus to maintain insertion order of passed flags, it is recommended that users use a scala.collection.immutable.ListMap or equivalent.

If you wish to test with toggled values of a GlobalFlag for an embedded app or server, you can still use Flag.let or Flag.letClear in tests. Note that if an embedded server takes globalFlags arguments, they will take precedents over an outer Flag.let. It is recommended to test using Flag.let or Flag.letClear on a GlobalFlag in places where the global flag value is read once and not treated as a runtime variable, otherwise behavior can be unexpected across thread boundaries. For example,

import com.twitter.finatra.http.EmbeddedHttpServer
import com.twitter.finagle.http.Status
import com.twitter.inject.server.FeatureTest
import scala.collection.immutable.ListMap

class ExampleServerFeatureTest extends FeatureTest {
  override val server = new EmbeddedHttpServer(
    twitterServer = new ExampleServer,
    globalFlags = ListMap(com.foo.bar.someGlobalFlag -> "a value")
  )

  test("ExampleServer#perform feature") {

    com.foo.bar.someGlobalFlag.let("b value") {
      // any read of the `com.foo.bar.someGlobalFlag` value in this closure will be "b value"

      com.foo.bar.someGlobalFlag() should equal("b value")

      // except execution within the server will see "a value" due to `globalFlags` scope
      server.httpGet(
        path = "/",
        andExpect = Status.Ok)

      ???
    }
  }
}

See the scaladoc for c.t.app.Flag for more information on using Flag.let or Flag.letClear.

InMemoryStatsReceiver

The EmbeddedTwitterServer (and thus its subclasses: EmbeddedHttpServer and EmbeddedThriftServer) binds an instance of the com.twitter.finagle.stats.InMemoryStatsReceiver to the underlying server’s object graph (if the underlying server supports injection). This will override any other bound implementation of a c.t.finagle.stats.StatsReceiver in the server’s object graph.

The EmbeddedTwitterServer exposes the bound StatsReceiver along with helper methods for asserting counter, stat, and gauge values, such that you can expect behavior against the underlying server’s recorded stats in tests.

Feature Tests also print all recorded stats to stdout after each test by default.

See: c.t.finatra.multiserver.test.MultiServerFeatureTest for an example usage.