In the following recipe, we will replace an existing bean with a test double using Jukito annotations (since this library has a specially defined JUnit runner, it integrates perfectly with JUnit—there is no official support for TestNG).
In order to profit from Jukito, you have to add it to your build. The following is the configuration for Gradle:
testCompile 'org.jukito:jukito:1.4'
A sample Maven dependency configuration is given as follows:
<dependency> <groupId>org.jukito</groupId> <artifactId>jukito</artifactId> <version>1.4</version> </dependency>
We will reuse the previous example of the tax transferring system for a given person, as shown in the following code:
public class TaxTransferer { private final TaxService taxService; public TaxTransferer(TaxService taxService) { this.taxService = taxService; } public boolean transferTaxFor(Person person) { if (person == null) { return false; } taxService.transferTaxFor(person); return true; } }
Where the TaxService
is an interface that has an implementation called TaxWebService
that makes the web service call, as shown in the following code (for simplicity, we are only writing that we are performing such data exchange):
class TaxWebService implements TaxService { @Override public void transferTaxFor(Person person) { System.out.printf("Calling external web service for person with name [%s]%n", person.getName()); } }
The following is a snippet with Guice's module configuration:
public class TaxModule extends AbstractModule { @Override protected void configure() { bind(TaxService.class).to(TaxWebService.class); } }
To integrate JUnit with Guice using Jukito, you have to perform the following steps:
@RunWith(JukitoRunner.class)
.@UseModules
Jukito annotation.@UseModules
annotation (note that Jukito needs the module to be publicly accessible) and @Inject
those fields to the test.@UseModules
annotation.To test our system using Jukito, you have to do the following (it's not using a separate module but passes the dependencies to be mocked as test method parameters):
@RunWith(JukitoRunner.class) public class TaxTransfererTest { @Inject TaxTransferer taxTransferer; @Test public void should_transfer_tax_for_person(TaxService taxService) { // given Person person = new Person(); // when boolean transferSuccessful = taxTransferer.transferTaxFor(person); // then then(transferSuccessful).isTrue(); verify(taxService).transferTaxFor(person); } }
If you have some more complex logic in your test module configuration, then you can provide it as a parameter of the @UseModules
annotation, as shown in the following code:
@RunWith(JukitoRunner.class) @UseModules({MockModule.class}) public class TaxTransfererUseModuleTest { @Inject TaxTransferer taxTransferer; @Inject TaxService taxService; @Test public void should_transfer_tax_for_person() { // given Person person = new Person(); // when boolean transferSuccessful = taxTransferer.transferTaxFor(person); // then then(transferSuccessful).isTrue(); verify(taxService).transferTaxFor(person); } }
The test module configuration may look like the following:
public class MockModule extends AbstractModule { @Override protected void configure() { bind(TaxService.class).toInstance(Mockito.mock(TaxService.class)); } }
You can also provide the configuration in the inner static class that extends JukitoModule
— in this way, you will access some handy helper methods such as bindMock(…)
, bindSpy(…)
, and so on). The following is an example depicting that and assuming that TaxService
is a class and not an interface (to show how to deal with spies):
@RunWith(JukitoRunner.class) public class TaxTransfererUseInnerJukitoModuleTest { @Inject TaxTransferer taxTransferer; @Inject TaxService taxService; public static class Module extends JukitoModule { protected void configureTest() { bindSpy(TaxService.class).in(TestScope.SINGLETON); } } @Test public void should_transfer_tax_for_person() { // given Person person = new Person(); doNothing().when(taxService).transferTaxFor(person); // when boolean transferSuccessful = taxTransferer.transferTaxFor(person); // then then(transferSuccessful).isTrue(); verify(taxService).transferTaxFor(person); } }
Jukito comes along with JukitoRunner
, which is a JUnit runner—that's why all the magic is done behind the scenes for you as follows:
@UseModules
annotation. If applicable, Jukito creates JukitoModule
with those modules.@UseModules
annotation, Jukito checks if you provided a static inner class that extends JukitoModule
and provides a test configuration for your test. If that is the case, Jukito creates JukitoModule
with the test module.JukitoModule
extension class in your test, Jukito instantiates a default JukitoModule
implementation (extension of Jukito's TestModule
).Guice.createInjector(...)
method and passes the instantiated JukitoModule
(by one of the aforementioned approaches).@All
annotated (more about this annotation in the There's more section)@Inject
annotated fields and create mocks for objects passed as arguments of the test methods.Jukito allows you to go further with testing Guice based applications—you can perform parameterized tests. The following is a test in which we verify that regardless of the country from which the person originates, the application sends a single message via a web service:
@RunWith(JukitoRunner.class) public class TaxTransfererParametrizedTest { @Inject TaxTransferer taxTransferer; public static class Module extends JukitoModule { protected void configureTest() { bindManyInstances(Person.class, new Person(), new Person("Poland"), new Person("France"), new Person("Germany")); } } @Test public void should_transfer_tax_for_person(TaxService taxService, @All Person person) { // when boolean transferSuccessful = taxTransferer.transferTaxFor(person); // then then(transferSuccessful).isTrue(); verify(taxService).transferTaxFor(person); } }
What Jukito does while executing the test is that it searches for the @All
annotated objects and it collects all bindings matching the type of the annotated argument. In the case of the previous snippet, it will collect four bindings of the person
instances from the static Module
class. Next, Jukito checks if there are more @All
annotated arguments. If that is the case, it will run the test as many as the size of the set resulting from the cartesian product. In the case of the previous test class, the should_transfer_tax_for_person
test will be executed four times. Please check Jukito's documentation for further details on the @All
annotation.
3.145.17.18