Nicholas Lativy

Acai – Guice for JUnit4 tests

I recently open sourced a small personal project for using Guice in JUnit4 tests called Acai.

The aim was to make it really simple to write functional tests of a set of services which use Guice and additionally to make it easy to compose the testing modules for multiple systems and write a test for their integration. A trivial example test using Acai might look like this:

@RunWith(JUnit4.class)
public class SimpleTest {
  @Rule public Acai acai = new Acai(MyTestModule.class);
  @Inject private MyClass foo;

  @Test
  public void checkSomethingWorks() {
    // Use the injected value of foo here
  }
}

This tests has its fields injected by Acai by simply passing an ordinary Guice module to the Acai @Rule. Of course using Guice in tests has long been possible with the help of Guiceberry and with JUnit4 rules it's pretty trivial to write your own rule that takes a module and injects fields in your test with a few lines of code.

The real power of Acai comes when you have a service that you want to start once and use in all tests, some cleanup you wish to do between tests and some objects you need access to in the tests themselves. A more sophisticated test demonstrating this would be:

@RunWith(JUnit4.class)
public class ExampleFunctionalTest {
  @Rule public Acai acai = new Acai(MyTestModule.class);
  @Inject private MyServerClient serverClient;

  @Test
  public void checkSomethingWorks() {
    // Call the running server and test some behaviour here.
    // Any state will be cleared by MyDatabaseWiper after each
    // test case.
  }

  private static class MyTestModule extends AbstractModule {
    @Override protected void configure() {
      // Normal Guice modules which configure your
      // server with in-memory versions of backends.
      install(MyServerModule());
      install(MyFakeDatabaseModule());

      install(new TestingServiceModule() {
        @Override protected void configureTestingServices() {
          bindTestingService(MyServerRunner.class);
          bindTestingService(MyDatabaseWiper.class);
        }
      });
    }
  }

  private static class MyServerRunner implements TestingService {
    @Inject private MyServer myServer;

    @BeforeSuite void startServer() {
      myServer.start().awaitStarted();
    }
  }

  private static class MyDatabaseWiper implements TestingService {
    @Inject private MyFakeDatabse myFakeDatabase;

    @AfterTest void wipeDatabase() {
      myFakeDatabase.wipe();
    }
  }
}

Here we introduce Acai's TestingService, the central part of Acai's API. Classes which implement this interface can add methods which run before all tests or between tests using the annotations @BeforeSuite, @BeforeTest and @AfterTest. Acai also provides an abstract base class TestingServiceModule which makes it easy to bind these testing services. You can configure as many testing services as you wish and they can be installed via as many TestingServiceModule implementations as is convenient.

This flexibility in being able to bind multiple services makes it easy to share testing configuration between components. For instance you may have a StorageTestingModule which you use for integration tests of repository classes with local or in-memory storage, you could then combine this with a BackendTestingModule to test your API and then finally combine futher with a FrontendTestingModule for Webdriver tests which give you confidence your entire stack works together. This offers extra convenience over GuiceBerry which currently requires clients to add their own layer of abstraction if they wish to start multiple services in a clean composable manner.

Once we start writing tests which start mutliple services for testing we often discover it is required or desirable to start them in a specific order. Acai makes this easy by providing a @DependsOn annotation which allows you to declaratively express such dependencies. For example:

@DependsOn(BackendTestService.class)
class FrontendTestService implements TestingService {
  @BeforeSuite
  void startFrontend() {
    // Start the frontend.
  }
}

The @DependsOn annotation here declares that methods in FrontendTestService must always be run after the corresponding methods in BackendTestService. Acai will observe all such annotations and order the execution of methods in your testing services accordingly.

I've been using Acai for the larger tests on my own projects and hope that others will find it useful too. You can report issues or fork the repository on GitHub.