Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Testing library for JUnit4 and Guice.

License

NotificationsYou must be signed in to change notification settings

google/acai

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

88 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Build StatusCoverage Status

Acai makes it easy to write functional tests of your applicationwith JUnit4 and Guice.

Acai makes it simple to:

  • Inject the helper classes you need into tests
  • Start any services needed by your tests
  • Run between-test cleanup of these services
  • Start up multiple services for testing in the right order
  • Create test scoped bindings

Acai is designed for large functional tests of your application. Forexample it can help with writing tests which start your backend and frontendserver in a self-contained mode with their dependencies faked out and thenvalidates some key user scenarios with Webdriver to give you confidence yourcomplete system works correctly. It can also be useful for tests which validatethe integration of a small set of components. Note however that for smallerunit-tests we generally recommend you create the class under test manuallyrather than using Acai.

Installation

Add a dependency oncom.google.acai:acai in your build system to fetch Acaiautomatically from Maven Central. For example, with Maven add the following toyour dependencies inpom.xml:

<dependency>  <groupId>com.google.acai</groupId>  <artifactId>acai</artifactId>  <version>1.1</version>  <scope>test</scope></dependency>

See theartifact details on Maven Centralfor dependency information for other build systems or to simply download thejars.

Using Acai to inject a test

The simplest test using Acai doesn't register any TestingService bindingsat all, it just uses Acai to inject a test with a module:

@RunWith(JUnit4.class)publicclassSimpleTest {@RulepublicAcaiacai =newAcai(MyTestModule.class);@InjectprivateMyClassfoo;@TestpublicvoidcheckSomethingWorks() {// Use the injected value of foo here  }privatestaticclassMyTestModuleextendsAbstractModule {@Overrideprotectedvoidconfigure() {bind(MyClass.class).to(MyClassImpl.class);    }  }}

Using Acai to start services

The real power of Acai comes when your production server is configuredwith Guice and you create an alternate test module which configures your serverwith heavyweight dependencies like databases replaced with local in-memoryimplementations. You could then start this server once for all tests in thesuite. For example:

@RunWith(JUnit4.class)publicclassExampleFunctionalTest {@RulepublicAcaiacai =newAcai(MyTestModule.class);@InjectprivateMyServerClientserverClient;@TestpublicvoidcheckSomethingWorks() {// Call the running server and test some behaviour here.  }privatestaticclassMyTestModuleextendsAbstractModule {@Overrideprotectedvoidconfigure() {// Normal Guice modules which configure your// server with in-memory versions of backends.install(MyServerModule());install(TestingServiceModule.forServices(MyServerRunner.class));    }  }privatestaticclassMyServerRunnerimplementsTestingService {@InjectprivateMyServermyServer;@BeforeSuitevoidstartServer() {myServer.start().awaitStarted();    }  }}

Note that when a module is passed to Acai in a rule any @BeforeSuitemethods are only executed once per suite even if the same module is used inmultiple Acai rules in multiple different test classes within that suite.This allows tests of the server to be structured into test classes according tothe functionality being tested.

Test isolation

When sharing a locally running backend or fake between multiple test cases asabove it's often necessary to clear its state between each test in order toisolate tests from one another.

This can be achieved using an@AfterTest method in aTestingService. Thefollowing example clears all data in a local database between tests:

@RunWith(JUnit4.class)publicclassExampleFunctionalTest {@RulepublicAcaiacai =newAcai(MyTestModule.class);@InjectprivateMyServerClientserverClient;@TestpublicvoidcheckSomethingWorks() {// Perform actions which write to the database here.// Any state will be cleared by MyFakeDatabaseWiper after each// test case.  }privatestaticclassMyTestModuleextendsAbstractModule {@Overrideprotectedvoidconfigure() {install(MyFakeDatabaseModule());install(TestingServiceModule.forServices(MyFakeDatabaseWiper.class));    }  }privatestaticclassMyFakeDatabaseWiperimplementsTestingService {@InjectprivateMyFakeDatabsemyFakeDatabase;@AfterTestvoidwipeDatabase() {myFakeDatabase.wipe();    }  }}

Test scoped bindings

Occasionally you may wish to have one instance of a class per test and injectthis instance in multiple places in the object graph. In this case Guice'sdefault instance scope will not do. Fortunately Acai provides a@TestScopedannotation which can be used to achieve exactly this.

For example we may define a module for using Webdriver (a popular browserautomation tool) in our tests like so:

classWebdriverModuleextendsAbstractModule {privatestaticfinalDurationMAX_WAIT =Duration.standardSeconds(5);@Overrideprotectedvoidconfigure() {install(newTestingServiceModule() {@OverrideprotectedvoidconfigureTestingServices() {bindTestingService(WebDriverQuitter.class);      }    });  }@Provides@TestScopedWebDriverprovideWebDriver() {// Provide the driver here; precisely one instance will be// created per test case.  }@ProvidesWebDriverWaitprovideWait(WebDriverwebDriver) {returnnewWebDriverWait(webDriver,MAX_WAIT.getStandardSeconds());  }staticclassWebDriverQuitterimplementsTestingService {@InjectProvider<WebDriver>webDriver;@AfterTestvoidquitWebDriver()throwsException {// Calling get on the Provider here returns the instance// for the test case which we are currently tearing down.webDriver.get().quit();    }  }}

One important point to note when using@TestScoped bindings is thatTestingService instances are instantiated once for all tests outside of testscope. Therefore if you wish to access@TestScoped bindings in a@BeforeTestor@AfterTest method you should inject aProvider and callget on itwithin those methods as shown in the above example.

When not to use TestScoped

Note that while@TestScoped works well for helpers injected only into tests(such as the WebDriver instance in the above example) for fakes and otherobjects which are shared with the system under test it is usually simpler to usea single instance and reset its state with aTestingService(seeTest isolation). This technique avoids some of the limitationsof@TestScoped such as the fact it can only be injected on the test thread or childthreads of the test and makes it possible to inject the instance into objects whoselifetime is longer than that of an individual test.

Services which depend upon each other

If the services you need to start for tests must be started in a specific orderyou can express this using the@DependsOn annotation.

For example:

@RunWith(JUnit4.class)publicclassExampleFrontendWebdriverTest {@RulepublicAcaiacai =newAcai(MyTestModule.class);@InjectprivateSomeFrontendFeaturePageObjectfeaturePage;@TestpublicvoidcheckSomethingWorks() {// Test the frontend client using the webdriver page// object here.  }privatestaticclassMyTestModuleextendsAbstractModule {@Overrideprotectedvoidconfigure() {// Normal Guice modules which configure your// server with in-memory versions of servers and// a test module configuring a webdriver client.install(MyServerModule());install(MyFakeDatabaseModule());install(WebDriverModule());install(newTestingServiceModule() {@OverrideprotectedvoidconfigureTestingServices() {bindTestingService(MyFrontendRunner.class);bindTestingService(MyBackendRunner.class);        }      });    }  }@DependsOn(MyBackendRunner.class)privatestaticclassMyFrontendRunnerimplementsTestingService {@InjectprivateMyFrontendServermyFrontendServer;@BeforeSuitevoidstartServer() {myFrontendServer.start().awaitStarted();    }  }privatestaticclassMyBackendRunnerimplementsTestingService {@InjectprivateMyBackendServermyBackendServer;@BeforeSuitevoidstartServer() {myBackendServer.start().awaitStarted();    }  }}

In the above exampleMyFrontendRunner is annotated@DependsOn(MyBackendRunner.class) which will cause Acai to start thebackend server before starting the frontend.

API

As shown in the above examples Acai has a relatively small API surface.Firstly, and most importantly, there is theAcai rule class itselfwhich is used as a JUnit4@Rule and is passed a module class to be used toconfigure the test.

The module class passed to theAcai constructor may optionally useTestingServiceModule to bind one or moreTestingService implementations.

TheTestingService interface is purely a marker to allow Acai to knowwhich classes provide testing services. To actually do anything implementationsof this interface should add zero argument methods annotated with one of@BeforeSuite,@BeforeTest or@AfterTest. These methods will be run beforethe suite, before each test or after each test respectively. You may add asmany methods annotated with these annotations as you wish to aTestingService; Acai will find and run them all when appropriate.

For more advanced use-cases where instance scope is not sufficient the@TestScoped annotation can be used to create one instance of a class per testcase.

Finally aTestingService implementation can be annotated@DependsOn tosignal its@BeforeSuite and@BeforeTest methods need to be run afterthose of anotherTestingService. This provides a simple declarative mechanismto order service startup in tests.

Refer to the examples above to see the API in action.

Contributing

We'd love to accept your patches and contributions to this project. There are ajust a few small guidelines you need to follow. See theCONTRIBUTING.md file for moreinformation.

Disclaimer

This is not an official Google product.


[8]ページ先頭

©2009-2025 Movatter.jp