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, there is a concept of a module.
Creation of a Guice module
Lets say we have a simple interface Greeter:
1 2 3
and implementation of this interface:
1 2 3 4 5
We can then create a new Guice module:
1 2 3 4 5
In this module, we are binding a
Greater interface as a singleton. You can call
bind() method many times, setting up your object graph.
Injection of module into the application
The injection of a Guice module is executed as one line of code inside
AppBootstrap class, like so:
1 2 3 4 5 6 7 8
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:
1 2 3 4 5 6 7
greeter (line 3) method is set by the framework and injected an instance of a
GreeterImpl into the
HelloController 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:
- 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 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):
1 2 3 4 5
and a new mock module we will use in tests:
1 2 3 4 5 6
Once we have this done, we can inject the mock module during the test of a controller:
1 2 3 4 5 6 7 8 9 10 11
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
messageto 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.
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:
1 2 3 4 5 6
You can chain the
override(..).with(..) for multiple services in the container. Internally ActiveWeb simply creates dynamic modules on the fly.
GreeterMock.class will replace the real implementation of
Greeter.class provided in the module (supposedly
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:
1 2 3 4
The framework will create a dynamic module, bind interfaces and implementations and will use it to create a new Injecotr 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:
1 2 3
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 by this test.
Do not forget to call a 'terminal' method
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