Injecting test doubles instead of beans with Guice using Jukito

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).

Getting ready

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);
    }

}

How to do it...

To integrate JUnit with Guice using Jukito, you have to perform the following steps:

  1. Annotate your test class with @RunWith(JukitoRunner.class).
  2. In your integration test, if required, reference all the required production modules using the @UseModules Jukito annotation.
  3. To mock a component, you have to either pass the interfaces to be mocked as test methods arguments or pass a test module to the @UseModules annotation (note that Jukito needs the module to be publicly accessible) and @Inject those fields to the test.
  4. To create a spy of a component, you have to pass a test module to the @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);
    }

}

How it works...

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:

  • Retrieving all the passed modules to the @UseModules annotation. If applicable, Jukito creates JukitoModule with those modules.
  • If there is no @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.
  • If there is no such JukitoModule extension class in your test, Jukito instantiates a default JukitoModule implementation (extension of Jukito's TestModule).
  • Jukito creates an Injector using the Guice.createInjector(...) method and passes the instantiated JukitoModule (by one of the aforementioned approaches).
  • Jukito then goes through test methods and checks whether there are objects passed to the test methods and verifies if either of them is @All annotated (more about this annotation in the There's more section)
  • Jukito together with Guice injects all the necessary objects into the @Inject annotated fields and create mocks for objects passed as arguments of the test methods.

There's more...

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.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.17.18