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.
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); } }
In order to integration test the system and replace the bean with a mock, you have to perform the following steps:
AbstractModule
class) that will bind the class to be mocked with an actual mock or spy.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)); } }
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); } }
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); } }
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.
To change that behavior, you would have to reset the mock by calling Mockito.reset(mock1, mock2…mockn)
and then stub the mock again.
3.14.144.216