Integration Tests

Important

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

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.

c.t.inject.app.TestInjector

Whereas Feature Tests start a server or app under test (thereby loading its entire object graph), integration tests generally only test across a few interfaces in the system. In Finatra, we provide the c.t.inject.app.TestInjector which allows you to pass it a set of TwitterModules and Flags to construct a minimal object graph.

Important

Because TwitterModules differ from regular Guice Modules, it is important to use the c.t.inject.app.TestInjector for creating an object graph over a set of TwitterModules.

The TestInjector properly handles lifecycle execution and Flag parsing.

Manually creating a c.t.inject.Injector over a raw Guice Injector created from TwitterModules will skip both the lifecycle functions and Flag parsing of any Flag instances defined in the set of TwitterModules.

To write an integration test, extend the c.t.inject.IntegrationTest trait. Then override the injector val with your constructed instance of a c.t.inject.Injector created from the c.t.inject.app.TestInjector.

You’ll then be able to access instances of necessary classes to execute tests.

import com.twitter.inject.IntegrationTest
import com.twitter.inject.app.TestInjector

class ExampleIntegrationTest extends IntegrationTest {
  override val injector: TestInjector =
    TestInjector(
      flags = Map("foo.flag" -> "meaningfulValue"),
      modules = Seq(ExampleModule)
    ).create()

  test("MyTest#perform feature") {
    val exampleThingy = injector.instance[ExampleThingy]
    ...
  }
}

Caution

The injector is specified as a def the in c.t.inject.IntegrationTestMixin trait. If you only want to start one instance of your injector per test file make sure to override this def with a val.

The TestInjector also allows you to test modules’ lifecycle logic via its start() and close() methods. Note that the TestInjector doesn’t take ownership of modules’ lifecycle and, instead, leaves this exercise to the users. Put this way, you’re responsible for calling start() and close() in your test if you want to execute the lifecycle hooks in modules.

import com.twitter.inject.IntegrationTest
import com.twitter.inject.app.TestInjector

class ExampleLifecycleTest extends IntegrationTest {
  override val injector: TestInjector = TestInjector(
    flags = Map("foo.flag" -> "meaningfulValue"),
    modules = Seq(ExampleModule)
  ).create()

  override def beforeAll(): Unit = {
    // NOTE: This may throw an exception if any of your startup callbacks throw.
    injector.start()
  }

  override def afterAll(): Unit = {
    // NOTE: This may throw an exception if any of your shutdown callbacks throw.
    injector.close()
  }

  test("MyTest#perform feature") {
    val exampleThingy = injector.instance[ExampleThingy]
    ...
  }
}

Note

You are not required to extend com.twitter.inject.IntegrationTest for integration testing. You can choose to instead just extend com.twitter.inject.Test and implement everything above as well.

Finatra provides the com.twitter.inject.IntegrationTest utility as a placeholder to provide future functionality on the basis that the test code is aware of the injector member variable and is thus able to add functionality on top it.

bind[T] DSL

The c.t.inject.app.TestInjector also supports the bind[T] DSL for overriding bound types. See the bind[T] documentation for more information.

Practically, this means that you can create an object graph from the list of modules provided to the TestInjector and then swap out any bound instances in that object graph with a differing implementation without needing to explicitly provide any override modules. This can be particularly useful when working with Mocks in a test.

Http Tests

If you are writing a test that has an HTTP server under test, you can also extend the c.t.finatra.http.HttpTest trait. This trait provides some common utilities for HTTP testing, specifically utilities for constructing a resolverMap flag value for setting on your server under test.

Thrift Tests

Thrift servers can be tested through a c.t.finatra.thrift.ThriftClient. The Finatra test framework provides an easy way get access to a real Finagle client for making calls to your running server in a test.

See the Feature Tests - Thrift Server section for more information on creating a Finagle Thrift client.

Additionally, your test can also extend the c.t.finatra.thrift.ThriftTest trait which provides a utility specifically for constructing a resolverMap flag value for setting on your server under test.

Injecting Members of a Test

Warning

Do not inject members of a test class into the server, application or TestInjector object graph under test.

For an explanation of why, see the documentation here.