Dependency injection
Dependency injection is an integral part of a typical Java application. ActiveWeb supports seamless integration with Google Guice.
At the heart of a Google Guice DI, is a concept of a module.
Creation of a Guice module
Let?s say we have a simple interface Greeter:
and implementation of this interface:
public class GreeterImpl implements Greeter{
public String greet() {
return "Hello from real greeter";
}
}
We can then create a new Guice module:
public class GreeterModule extends AbstractModule {
protected void configure() {
bind(Greeter.class).to(GreeterImpl.class).asEagerSingleton();
}
}
In this module, we are binding a GreeterImpl
to a Greater
interface as a singleton. You can call the bind()
method many times, setting up your object graph.
Injection of a module into the application
The injection of a Guice module is executed as one line of code inside AppBootstrap
class, like so:
public class AppBootstrap extends Bootstrap {
public void init(AppContext context) {}
protected Injector getInjector(){
return Guice.createInjector(new GreeterModule());
}
}
The Guice.createInjector(..)
takes a varargs, meaning you can inject multiple modules at once.
Consumption of injected dependencies
Whenever you need a service inside a controller, you will use an @Inject
annotation provided by Google Guice:
public class HelloController extends AppController {
@Inject
private Greeter greeter;
public void index(){
view("message", greeter.greet());
}
}
The greeter
(line 3) method is set by the framework and injects an instance of a GreeterImpl
into the HelloController
the controller just before it executes an action. Once the controller has a reference to the service, it can consume it (line 5).
Where can you inject dependencies this way? There are three general application components that are injected dependencies:
- Controllers
- Controller Filters
- Custom Tags
The technique is exactly the same, just add @Inject annotation that requires a service from a Guice module, and you can use it in code inside the component
Mocking and testing
In testing, it is typical to replace real implementation of services with mocks. For an explanation of mocks and stubs, follow this link http://martinfowler.com/articles/mocksArentStubs.html.
Why would someone want to use a mock instead of a real implementation? Here are some reasons:
- Real implementation submits a sensitive transaction (you do not want that during a build!)
- Real implementation requires a network connection to external resource
- Real implementation is really? slow.
- Real implementation does not always cover all conditions in your code at a given time
- many others
when you use a real implementation of a service in tests, your test is not only testing your code, but also the implementation of a service, mushing everything together.
Now, lets create an mock service (this can also be created using any other mocking libraries, such as Mockito):
public class GreeterMock implements Greeter{
public String greet() {
return "Hello from " + this.getClass().toString();
}
}
and a new mock module we will use in tests:
public class GreeterMockModule extends AbstractModule {
@Override
protected void configure() {
bind(Greeter.class).to(GreeterMock.class).asEagerSingleton();
}
}
Once we have this done, we can inject the mock module during the test of a controller:
public class HelloControllerSpec extends ControllerSpec {
@Before
public void before(){
setInjector(Guice.createInjector(new GreeterMockModule()));
}
@Test
public void shouldInjectMockService(){
request().get("index");
a(val("message")).shouldBeEqual("Hello from class app.services.GreeterMock");
}
}
Lets examine this test line by line:
- Line 4 - this is where we tell the test scaffolding which module to use, and we chose a mock module
- Line 8 - we construct a GET request for HelloController, action
index
and execute the controller. - Line 9 - we inspect that the controller did in fact send a
message
to a view, but the value of this message will be generated by the mock service.
Please, refer to Kitchensink for a working example of DI in ActiveWeb.
Easier Mocking
While Google Guice provides a way of mocking by creating a new mock module, it feels heavy-handed. ActiveWeb specs provide a more elegant API for mocking:
@Test
public void shouldOverrideWithMock(){
setInjector(createInjector(new GreeterModule()).override(Greeter.class).with(GreeterMock.class).create());
request().get("index");
a(responseContent()).shouldBeEqual("The greeting is: Hello from mock greeter");
}
You can chain the override(..).with(..)
for multiple services in the container. Internally ActiveWeb simply creates dynamic modules on the fly.
Here, the GreeterMock.class
will replace the real implementation of Greeter.class
provided in the module (supposedly GreeterImpl.class
).
Easier Yet Mocking
In some cases you will not want to create a new module class for one or two services. There is an easier way to set your service interfaces and implementations without a new module.
Here is one example:
public void before(){
Injector i = injector().bind(Greeter.class).to(GreeterMock.class)
.bind(Redirector.class).to(RedirectorImpl.class).create();
}
The framework will create a dynamic module, bind interfaces and implementations and use it to create a new Injector on the fly.
Sometimes, you will only have a single service class not broken into an interface and implementation. In such a case, the to()
method is optional:
The instance of a new injector will be added to the current context and will be used to inject services into filters and controllers executing this test.
Easier Yet Mocking with provided instances
In some cases, you still want to be in control of creating an instance of a service to be injected. It could be a more complex class, a Mockito-generated mock, or anything else you need. You will then be able to use the toInstance()
method to add an instance. Here is a snippet from the JavaLite sources:
public void shouldOverrideWithMockitoInstance(){
mock(Greeter.class);
Greeter mock = Mockito.when(mock.greet()).thenReturn("hello there!!!");
injector().bind(Greeter.class).toInstance(mock).create();
request().get("index");
a(responseContent()).shouldBeEqual("The greeting is: hello there!!!");
}
The line that starts with injector()
uses the toInstance(..)
method, which allows you to bind a pre-initialized instance to an interface passed to the bind(..)
method.
Do not forget to call a terminal
method create()
How to comment
The comment section below is to discuss documentation on this page.
If you have an issue, or discover bug, please follow instructions on the Support page