{"id":25776225,"url":"https://github.com/google/acai","last_synced_at":"2026-04-26T23:01:42.138Z","repository":{"id":23774096,"uuid":"27149168","full_name":"google/acai","owner":"google","description":"Testing library for JUnit4 and Guice.","archived":false,"fork":false,"pushed_at":"2023-06-20T22:20:57.000Z","size":127,"stargazers_count":76,"open_issues_count":8,"forks_count":28,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-01-07T16:07:13.059Z","etag":null,"topics":["acai","backend","frontend","guice","java","junit4"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-11-25T22:28:39.000Z","updated_at":"2024-12-05T16:27:22.000Z","dependencies_parsed_at":"2022-08-22T04:50:27.004Z","dependency_job_id":null,"html_url":"https://github.com/google/acai","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Facai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Facai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Facai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Facai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/acai/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240987435,"owners_count":19889333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acai","backend","frontend","guice","java","junit4"],"created_at":"2025-02-27T06:01:19.539Z","updated_at":"2026-01-11T03:42:03.974Z","avatar_url":"https://github.com/google.png","language":"Java","readme":"# Acai\n\n[![Build Status](https://travis-ci.org/google/acai.svg?branch=master)](\nhttps://travis-ci.org/google/acai)\n[![Coverage Status](https://coveralls.io/repos/google/acai/badge.svg)](\nhttps://coveralls.io/r/google/acai?branch=master)\n\nAcai makes it easy to write functional tests of your application\nwith JUnit4 and Guice.\n\nAcai makes it simple to:\n - Inject the helper classes you need into tests\n - Start any services needed by your tests\n - Run between-test cleanup of these services\n - Start up multiple services for testing in the right order\n - Create test scoped bindings\n\nAcai is designed for large functional tests of your application. For\nexample it can help with writing tests which start your backend and frontend\nserver in a self-contained mode with their dependencies faked out and then\nvalidates some key user scenarios with Webdriver to give you confidence your\ncomplete system works correctly. It can also be useful for tests which validate\nthe integration of a small set of components. Note however that for smaller\nunit-tests we generally recommend you create the class under test manually\nrather than using Acai.\n\n## Installation\nAdd a dependency on `com.google.acai:acai` in your build system to fetch Acai\nautomatically from Maven Central. For example, with Maven add the following to\nyour dependencies in `pom.xml`:\n\n```XML\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.google.acai\u003c/groupId\u003e\n  \u003cartifactId\u003eacai\u003c/artifactId\u003e\n  \u003cversion\u003e1.1\u003c/version\u003e\n  \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nSee the [artifact details on Maven Central](\nhttp://search.maven.org/#artifactdetails|com.google.acai|acai|1.1|)\nfor dependency information for other build systems or to simply download the\njars.\n\n## Using Acai to inject a test\nThe simplest test using Acai doesn't register any TestingService bindings\nat all, it just uses Acai to inject a test with a module:\n\n```Java\n@RunWith(JUnit4.class)\npublic class SimpleTest {\n  @Rule public Acai acai = new Acai(MyTestModule.class);\n\n  @Inject private MyClass foo;\n\n  @Test\n  public void checkSomethingWorks() {\n    // Use the injected value of foo here\n  }\n\n  private static class MyTestModule extends AbstractModule {\n    @Override protected void configure() {\n      bind(MyClass.class).to(MyClassImpl.class);\n    }\n  }\n}\n```\n\n## Using Acai to start services\nThe real power of Acai comes when your production server is configured\nwith Guice and you create an alternate test module which configures your server\nwith heavyweight dependencies like databases replaced with local in-memory\nimplementations. You could then start this server once for all tests in the\nsuite. For example:\n\n```Java\n@RunWith(JUnit4.class)\npublic class ExampleFunctionalTest {\n  @Rule public Acai acai = new Acai(MyTestModule.class);\n\n  @Inject private MyServerClient serverClient;\n\n  @Test\n  public void checkSomethingWorks() {\n    // Call the running server and test some behaviour here.\n  }\n\n  private static class MyTestModule extends AbstractModule {\n    @Override protected void configure() {\n      // Normal Guice modules which configure your\n      // server with in-memory versions of backends.\n      install(MyServerModule());\n\n      install(TestingServiceModule.forServices(MyServerRunner.class));\n    }\n  }\n\n  private static class MyServerRunner implements TestingService {\n    @Inject private MyServer myServer;\n\n    @BeforeSuite void startServer() {\n      myServer.start().awaitStarted();\n    }\n  }\n}\n```\n\nNote that when a module is passed to Acai in a rule any @BeforeSuite\nmethods are only executed once per suite even if the same module is used in\nmultiple Acai rules in multiple different test classes within that suite.\nThis allows tests of the server to be structured into test classes according to\nthe functionality being tested.\n\n## Test isolation\n\nWhen sharing a locally running backend or fake between multiple test cases as\nabove it's often necessary to clear its state between each test in order to\nisolate tests from one another.\n\nThis can be achieved using an `@AfterTest` method in a `TestingService`. The\nfollowing example clears all data in a local database between tests:\n\n\n```Java\n@RunWith(JUnit4.class)\npublic class ExampleFunctionalTest {\n  @Rule public Acai acai = new Acai(MyTestModule.class);\n\n  @Inject private MyServerClient serverClient;\n\n  @Test\n  public void checkSomethingWorks() {\n    // Perform actions which write to the database here.\n    // Any state will be cleared by MyFakeDatabaseWiper after each\n    // test case.\n  }\n\n  private static class MyTestModule extends AbstractModule {\n    @Override protected void configure() {\n      install(MyFakeDatabaseModule());\n      install(TestingServiceModule.forServices(MyFakeDatabaseWiper.class));\n    }\n  }\n\n  private static class MyFakeDatabaseWiper implements TestingService {\n    @Inject private MyFakeDatabse myFakeDatabase;\n\n    @AfterTest void wipeDatabase() {\n      myFakeDatabase.wipe();\n    }\n  }\n}\n```\n\n## Test scoped bindings\nOccasionally you may wish to have one instance of a class per test and inject\nthis instance in multiple places in the object graph. In this case Guice's\ndefault instance scope will not do. Fortunately Acai provides a `@TestScoped`\nannotation which can be used to achieve exactly this.\n\nFor example we may define a module for using Webdriver (a popular browser\nautomation tool) in our tests like so:\n\n```java\nclass WebdriverModule extends AbstractModule {\n  private static final Duration MAX_WAIT = Duration.standardSeconds(5);\n\n  @Override\n  protected void configure() {\n    install(new TestingServiceModule() {\n      @Override protected void configureTestingServices() {\n        bindTestingService(WebDriverQuitter.class);\n      }\n    });\n  }\n\n  @Provides\n  @TestScoped\n  WebDriver provideWebDriver() {\n    // Provide the driver here; precisely one instance will be\n    // created per test case.\n  }\n\n  @Provides\n  WebDriverWait provideWait(WebDriver webDriver) {\n    return new WebDriverWait(webDriver, MAX_WAIT.getStandardSeconds());\n  }\n\n  static class WebDriverQuitter implements TestingService {\n    @Inject Provider\u003cWebDriver\u003e webDriver;\n\n    @AfterTest void quitWebDriver() throws Exception {\n      // Calling get on the Provider here returns the instance\n      // for the test case which we are currently tearing down.\n      webDriver.get().quit();\n    }\n  }\n}\n```\n\nOne important point to note when using `@TestScoped` bindings is that\n`TestingService` instances are instantiated once for all tests outside of test\nscope. Therefore if you wish to access `@TestScoped` bindings in a `@BeforeTest`\nor `@AfterTest` method you should inject a `Provider` and call `get` on it\nwithin those methods as shown in the above example.\n\n### When not to use TestScoped\nNote that while `@TestScoped` works well for helpers injected only into tests\n(such as the WebDriver instance in the above example) for fakes and other\nobjects which are shared with the system under test it is usually simpler to use\na single instance and reset its state with a `TestingService`\n(see [Test isolation](#test-isolation)). This technique avoids some of the limitations\nof `@TestScoped` such as the fact it can only be injected on the test thread or child\nthreads of the test and makes it possible to inject the instance into objects whose\nlifetime is longer than that of an individual test.\n\n## Services which depend upon each other\nIf the services you need to start for tests must be started in a specific order\nyou can express this using the `@DependsOn` annotation.\n\nFor example:\n\n```Java\n@RunWith(JUnit4.class)\npublic class ExampleFrontendWebdriverTest {\n  @Rule public Acai acai = new Acai(MyTestModule.class);\n\n  @Inject private SomeFrontendFeaturePageObject featurePage;\n\n  @Test\n  public void checkSomethingWorks() {\n    // Test the frontend client using the webdriver page\n    // object here.\n  }\n\n  private static class MyTestModule extends AbstractModule {\n    @Override protected void configure() {\n      // Normal Guice modules which configure your\n      // server with in-memory versions of servers and\n      // a test module configuring a webdriver client.\n      install(MyServerModule());\n      install(MyFakeDatabaseModule());\n      install(WebDriverModule());\n\n      install(new TestingServiceModule() {\n        @Override protected void configureTestingServices() {\n          bindTestingService(MyFrontendRunner.class);\n          bindTestingService(MyBackendRunner.class);\n        }\n      });\n    }\n  }\n\n  @DependsOn(MyBackendRunner.class)\n  private static class MyFrontendRunner implements TestingService {\n    @Inject private MyFrontendServer myFrontendServer;\n\n    @BeforeSuite void startServer() {\n      myFrontendServer.start().awaitStarted();\n    }\n  }\n\n  private static class MyBackendRunner implements TestingService {\n    @Inject private MyBackendServer myBackendServer;\n\n    @BeforeSuite void startServer() {\n      myBackendServer.start().awaitStarted();\n    }\n  }\n}\n```\n\nIn the above example `MyFrontendRunner` is annotated\n`@DependsOn(MyBackendRunner.class)` which will cause Acai to start the\nbackend server before starting the frontend.\n\n## API\nAs shown in the above examples Acai has a relatively small API surface.\nFirstly, and most importantly, there is the `Acai` rule class itself\nwhich is used as a JUnit4 `@Rule` and is passed a module class to be used to\nconfigure the test.\n\nThe module class passed to the `Acai` constructor may optionally use\n`TestingServiceModule` to bind one or more `TestingService` implementations.\n\nThe `TestingService` interface is purely a marker to allow Acai to know\nwhich classes provide testing services. To actually do anything implementations\nof this interface should add zero argument methods annotated with one of\n`@BeforeSuite`, `@BeforeTest` or `@AfterTest`. These methods will be run before\nthe suite, before each test or after each test respectively. You may add as\nmany methods annotated with these annotations as you wish to a\n`TestingService`; Acai will find and run them all when appropriate.\n\nFor more advanced use-cases where instance scope is not sufficient the\n`@TestScoped` annotation can be used to create one instance of a class per test\ncase.\n\nFinally a `TestingService` implementation can be annotated `@DependsOn` to\nsignal its `@BeforeSuite` and `@BeforeTest` methods need to be run after\nthose of another `TestingService`. This provides a simple declarative mechanism\nto order service startup in tests.\n\nRefer to the examples above to see the API in action.\n\n## Contributing\nWe'd love to accept your patches and contributions to this project. There are a\njust a few small guidelines you need to follow. See the [CONTRIBUTING.md](\nhttps://github.com/google/acai/blob/master/CONTRIBUTING.md) file for more\ninformation.\n\n## Disclaimer\nThis is not an official Google product.\n","funding_links":[],"categories":["测试"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Facai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2Facai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Facai/lists"}