With the latest performance enhancements and great dynamic, functional and meta-programming features Groovy becomes a very good choice not only for scripting, but also for big and complex application building. Long term complex application development requires extensive unit testing utilization to allow moving forward without breaking existing features. Groovy plays nicely with powerful Spring framework, which can be used to make application easily extensible and unit testing friendly.
In this article we will use all the latest approaches to inversion of control using powerful Spring Framework, unit testing and build tools utilization to show how to make good architecture of non-trivial Groovy application.
Let's take a look at the Main class of the application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
Here we configure Spring to use XML-less annotation based dependency injection. The IoC configuration is located in the AppConfig class. After that we instantiate Application bean using Spring and pass control to it by calling run method.
The most interesting place in the code is environment initialization. The Environment is a relatively new feature, that
was added to Spring 3.1. Environment allows configuring Spring application context using values from external sources.
Here we push command line arguments into Environment so that they will be available in different parts of our
application later. We use MapPropertySource
for this purpose. It lets pass a dictionary of arbitrary
Objects into Environment.
Lets take a look at our AppConfig now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Here we instruct Spring to scan com.sysgears.example
to search for components and also to use JSR 330
annotations. JSR 330 is the standard of dependency injection for Java applications.
Notice, how we inject Environment into configuration using JSR330 @Inject
annotation and retrieve objects
from it. Note also, that Environment could be injected only to Spring @Configuration
classes.
The source code for GreetInfo
is trivial:
1 2 3 4 5 6 7 8 9 10 11 |
|
By default JSR 330 components have prototype scope, which means that new instance of the component will be
created each time it will be wired. To mark the component as having prototype-scope one should add
@Named-annotation. Below is the example of trivial Greeter
prototype-scoped component that
constructs greeting string. This component has dependency on the GreeterCounter
that holds
global counter of the number of times the Greeter
was instantiated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
The GreeterCounter
should be instantiated one time for a whole application. For this to happen
we should mark it with @javax.inject.Singleton
annotation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
So, @Named
and @javax.inject.Singleton
are the primary mechanisms to control how
many instances of your classes will be created. However these mechanisms only work during wiring phase, but what
if you want create class instances at runtime?
JSR 330 provides @javax.inject.Provider
- a separate mechanism to create instances at runtime.
Here is an example of using this mechanism:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Greeter
to output greeting text two times, using two greeter instances.
The instances of Greeter
are created with injected @Inject Provider
.
Note, that this is enough to write only this line of code to get factory of Greeter's created for us automatically by
IoC container at runtime.
This is pretty much all the logic of our pet application. Now, how to write unit tests for it? Let's look for the
simple case - a unit test for the Greeter
.
1 2 3 4 5 6 7 8 9 |
|
Unit tests are implemented using Spock
framework. Greeter
is pretty much independent and
simple class. That's why the unit test for this class is simple as well.
In this code we point Spring to the IoC configuration of our application, using
@ContextConfiguration(classes = AppConfig.class)
. Also we let Spring create the instance of Greeter class
and wire its dependencies by using just @Inject
annotated field.
The code is very neat and straightforward.
Let's handle more complex problem - a unit test for the class that depends on Environment
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@ContextConfiguration(classes = AppConfig.class, initializers = TestContextInitializer.class)
class MultiGreeterSpec extends Specification {
private @Inject MultiGreeter multiGreeter
public static class TestContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext ctx) {
def appProps = new MapPropertySource("appProps", [
greetInfo: new GreetInfo(text: "TestHello", name: "TestWorld")
] as Map<String, Object>)
ctx.getEnvironment().getPropertySources().addFirst(appProps);
}
}
def 'check greeting generation'() {
expect: 'should generate correct greeting'
multiGreeter.greet().trim() == '''
TestHello, TestWorld! (from greeter 1)
TestHello, TestWorld! (from greeter 2)
'''.trim()
}
}
Here in @ContextConfiguration
we point Spring to context initializer - e.g. the code that will be executed
right before wiring. We use context initializer to push test environment properties into application context. This is
very similar to the code we had in Main.groovy
to pass external properties into Spring context.
At the end of this post I would like to share the Gradle script to build and test this application and create executable JAR that can be launched on any computer that have Java VM installed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
This script is pretty-much self-explaining, however you need to make some magic, in order all things to work as expected. I hope this will be improved in the future versions of Gradle and Spock framework.
The full source code of the project is available at: GitHub repository.
Thanks for reading,