Injecting test doubles instead of beans with Guice

In this recipe, we will replace an existing bean with a test double using Guice's (https://code.google.com/p/google-guice/) module configuration.

Getting ready

Let's assume that our system under test is 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 class is an interface that has an implementation called TaxWebService, which 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:

class TaxModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(TaxService.class).to(TaxWebService.class);
    }

}

How to do it...

In order to integration test the system and replace the bean with a mock, you have to perform the following steps:

  1. Ensure that the component that you are going to mock is in a separate Guice module.
  2. Create an additional test module (by extending the AbstractModule class) that will bind the class to be mocked with an actual mock or spy.
  3. In your integration test, remember to reference all the necessary production modules.
  4. Instead of providing the production module with the component to mock, pass the test module with the mocked version of that component.

The following snippet shows the separate Guice test module:

public class MockModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(TaxService.class).toInstance(Mockito.mock(TaxService.class));
    }

}

Note

It is feasible to override an existing binding by calling Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule()));.

However, the javadoc for Modules.overrides(..) recommends that you design your modules in such a way that you don't need to override bindings. The solution to this is to move the classes to be mocked to separate modules.

Now let's take a look at the JUnit test (note that both tests are using the BDDAssertions static imports—please refer to Chapter 7, Verifying Behavior with Object Matchers, for theAssertJ configuration):

@RunWith(MockitoJUnitRunner.class)
public class TaxTransfererTest {

    TaxTransferer taxTransferer;

    TaxService taxService;

    @Before
    public void setup() {
        Injector injector = Guice.createInjector(new MockModule());
        taxTransferer = injector.getInstance(TaxTransferer.class);
        taxService = injector.getInstance(TaxService.class);
    }

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

Note

If using pure JUnit, you have to get the instances from Injector yourself—as we do here in the @Before part of your test by calling the Guice.createInjector(...) static method. You can also create your own TestRunner or use a library that will do all of the previous for you—for example Jukito.

The corresponding TestNG test is, as shown in the following code:

@Guice(modules = MockModule.class)
public class TaxTransfererTestNgTest {

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

Note

You can see that the test for TestNG looks much nicer in comparison to JUnit—thanks to the special @Guice TestNG annotation. It accepts as one of the parameters the modules that should be taken into consideration while setting up the context of the application.

How it works...

How Guice internally works is beyond the scope of this book, but let's take at least a high overview of what has happened in the previous snippets.

For JUnit, we have called the Guice.createInjector(...) static method that takes as arguments the modules from which it should build the Injector. The Injector, as the javadoc states, builds the graphs of objects that make up your application but should extremely rarely be called in the production code. It breaks the concept of DI and goes towards a Service Locator pattern (refer to http://martinfowler.com/articles/injection.html#UsingAServiceLocator). Anyway, we are calling it in the test environment since we want to perform integration testing, thus we want our dependencies to be initialized by Guice. Behind the scenes, Guice builds the graph of objects in such a way that when we call the injector.getInstance(...) method, it has all the binding information and returns the properly instantiated objects—that's why it takes into consideration our test binding of the TaxService interface to the mock of that interface.

For TestNG, the situation is much clearer—it produces far less boilerplate code. Guice has its own @Guice annotation that as one of the parameters accepts the modules that make up for the application. Behind the scenes, TestNG in the ClassImpl class instantiates or reuses the Guice injector in exactly the same way as we manually do it for JUnit—it passes the Guice modules classes to the Guice.createInjector(...) static method and then caches it in a map whose keys contain the aforementioned modules and the value is the injector as such. This map is present in the TestRunner class.

In the previous examples, we had a single test and we didn't explicitly stub any of the mock's methods. For the JUnit example, before each test we are creating a new injector so that a new mock is created. That means if you had two tests and you stubbed a mock's method in the first one, then the second one wouldn't see that stubbing. For TestNG, the situation is different.

Note

Remember that for TestNG the modules are shared between tests. In other words, once stubbed, your mock will be reused in all of your tests!

To change that behavior, you would have to reset the mock by calling Mockito.reset(mock1, mock2…mockn) and then stub the mock again.

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