INTRO
In the last post I clearly became more
independently of Spring, not because I hate him, in fact I like
Spring a lot. But to postpone the choice of the wiring/infrastructure
technology until the production stage, and to encourage
standardization and simplicity.
Just for that? No!
In many domains, all bleeding-edge Java
framework/toolkit seems to honor the JSR specifications, at the same
time JSR specifications very often receive continuous feedback from
concrete implementations, some examples are: JPA (JSR-220) with
Hibernate, Bean Validation (JSR-303) with Hibernate Validator and
Apache Beans Validator, and our main focus CDI (JSR-299) and Spring.
Spring actually honors CDI annotations, and if the leading framework
on DI (Spring) honors the standard, then there is a big chance for
both to be aligned with the future.
As you may noticed I used Spring
[before] on the testing phase to wire up the SUT and dependencies
with a relative small complexity. Nevertheless simple and small is
better, that's why I'll show you how achieve a very similar wiring
scenario w/o Spring (in testing phase) with a more simple approach
IMO.
There's an open question: Do we
still need Spring? Where does it fit?
The trivial answer is: Yes there's no a
DI framework as mature as Spring. It fits in the Integration-Test
(IT) phase very well.
NOTE: Take these things into
account when you read this post: 1) it's a simplified scenario, 2)
there MANY other phases besides of compilation, test, integration
tests, etc. where Spring also shines, 3) Spring is much more than an
DI framework.
HANDS ON LAB
So we need to replace the wiring
benefits of Spring in the early testing stage (don't confuse with IT)
showed on the [last post] with a worthy substitute. What we mean with
worthy? Two things: 1) it solves the wiring at this stage, and 2)
it's easy to use and reproduce.
Testing Phase
In the Java world many mocking
frameworks exists, I performed a research on this topic and found up
a mighty combination in the mocking area: PowerMock w/ Mockito.
PowerMock is the nearest similar thing
to a silver bullet I have ever seen in the mocking area, you can
combine it with Mockito. By the other hand Mockito is very user
friendly, simple and easy to learn. I recommend both in combination
for more elaborated tests, and only Mockito for simple scenarios like
this.
pom.xml
org.mockito
mockito-all
1.9.5
test
To achieve the same goal simplifying
things we need to inject the SUT's dependencies:
EditorTest.java
package demos.sf.editor.test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import demos.sf.editor.SpellChecker;
import demos.sf.editor.impl.Editor;
@RunWith(MockitoJUnitRunner.class)
public class EditorTest {
@InjectMocks
private Editor editor;
@Mock
private SpellChecker spellChecker;
@Test
public void testPasteSuccess() {
editor.paste("Hello everybody!");
String expected = "Hello everybody!";
String actual = editor.getText();
assertEquals(expected, actual);
}
@Test
public void testAddParagraphSpellIsChecked() {
editor.paste("Hello everybody!");
verify(spellChecker, only()).check("Hello everybody!");
}
}
Observe the introduction of:
@Mock: annotates a dependency to be
mocked.
@InjectMocks: annotates the SUT to be
instantiated and its @Mocks dependencies to get injected.
@RunWith(MockitoJUnitRunner.class):
uses the Mockito runner for tests.
Therefore, there's no need for DI
framework (neither application context configuration file) in test
phase at least for scenarios like this. Of course the injection and
mocking capacities of Mockito are limited compared to Spring.
IT Phase
In the IT phase you replace mock
objects with real ones, followed by a verification of the real
objects' combination performing as you expect. Let's show one of such
possible object graph where I use an English implementation for spell
checking, dictionary and tokenizer:
By replacing every mock for a real
object at time, you gradually introduce a full IT. You can achieve a
complete mock-replacement using a top-down approach, there
may be other ways of course but
I took this one:
(1) test Editor using
SpellChecker's mock
(2) replace SpellChecker's mock
for EnglishSpellChecker and use Dictionary and
Tokenizer mocks, then test Editor
(3) replace Dictionary's mock
for OxfordDictionary, then
test Editor
(4) replace Tokenizer's mock for
EnglishTokenizer, then test
Editor
Two new interfaces
appear in the demos.sf.editor package, they represent the
abstractions mentioned above:
public interface Dictionary {
String lookup(String word);
}
public interface Tokenizer {
Collection tokenize(String text);
}
Three new classes
appear in the demos.sf.editor.english package, they represent
and English wiring combination:
@Named
public class EnglishSpellChecker implements SpellChecker {
@Resource
private Tokenizer tokenizer;
@Resource
private Dictionary dictionary;
public String check(String text) {
...
}
}
@Named
public class EnglishTokenizer implements Tokenizer {
public Collection tokenize(String text) {
// TODO Auto-generated method stub
return null;
}
}
@Named
public class OxfordDictionary implements Dictionary {
public String lookup(String word) {
// TODO Auto-generated method stub
return null;
}
}
The beauty behind
is that out Editor remains the same:
@Named
public class Editor {
@Getter
protected String text;
@Resource
private SpellChecker spellChecker;
public void paste(String cut) {
if (text == null) {
text = "";
}
text += cut;
spellChecker.check(cut);
}
}
And there is a
change for introducing and test for integration by only changing the
IT application context, observe the component-scan with
demos.sf.editor.english package which instantiates the desired
combination of beans:
it-applicationContext.xml
There are
additional aliasing entries due to the fact that the injection is
performed on-resource-basis (by bean name), remember @Resource and
@Named annotations:
@Named
public class Editor {
// the following injects a bean named spellChecker
@Resource
private SpellChecker spellChecker;
...
}
// the following produces a bean named englishSpellChecker
@Named
public class EnglishSpellChecker implements SpellChecker {
// the following injects a bean named tokenizer
@Resource
private Tokenizer tokenizer;
// the following injects a bean named dictionary
@Resource
private Dictionary dictionary;
...
}
// the following produces a bean named englishTokenizer
@Named
public class EnglishTokenizer implements Tokenizer {
...
}
// the following produces a bean named oxfordDictionary
@Named
public class OxfordDictionary implements Dictionary {
...
}
You may want to inject based on type instead of name, it's easy to achieve by replacing the @Resource annotations with @Inject. Lets resume again the meaning of these annotations:
* @Resource:
Matching: First matches by
name, then matches by type and filters by qualifiers if not
name-based match gets found.
Applicable to: Fields,
Classes and Methods.
Package: javax.annotation
P { margin-bottom: 0.08in; }A:link { }
@Inject:
Matching: First
matches by type, filters by qualifiers, then matches be name.
Applicable to: Methods,
Constructors and Fields.
Package: javax.inject
@Named:
Description: Its a
qualifier that denotes a named bean. Annotated classes are
considered for auto-detection on classpath scanning. It's similar
to Spring's @Component
Applicable to: Classes and
Fields.
- Package: javax.inject
How to change it
to Spanish?
1- First implement
the three corresponding classes in demos.sf.editor.spanish
package, they will represent the desired wiring combination:
@Named
public class SpanishSpellChecker implements SpellChecker {
@Resource
private Tokenizer tokenizer;
@Resource
private Dictionary dictionary;
public String check(String text) {
...
}
}
@Named
public class SpanishTokenizer implements Tokenizer {
public Collection tokenize(String text) {
// TODO Auto-generated method stub
return null;
}
}
@Named
public class RAEDictionary implements Dictionary {
public String lookup(String word) {
// TODO Auto-generated method stub
return null;
}
}
Create
a new IT application context, or
change the existing one with a
component-scan
with demos.sf.editor.spanish
package which instantiates the desired combination of Spanish
beans :)
A
final step (really the first one)
Create
the integration test in the package demos.sf.editor.it,
it's very similar to the unit test case in the [previous post], the
main differences are the use of it-applicationContext.xml
as
context configuration, and the IT
suffix on the test case name:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/it-applicationContext.xml" })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class })
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class EditorIT {
@Resource
private Editor editor;
@Test
public void testPasteWorks() {
editor.paste("Hello everybody!");
String expected = "Hello everybody!";
String actual = editor.getText();
assertEquals(expected, actual);
}
}
To honor the IT test case only in the integration test phase the maven-failsafe-plugin may be used, hence the mvn test execution only runs the traditional JUnit test cases and ignores the IT test case classes suffixed with IT:
pom.xml
maven-failsafe-plugin
2.13
failsafe-integration-tests
integration-test
integration-test
verify
To run IT test then execute:
$ mvn verify
REFERENCES
CDI
/ JSR-299: