Look Mate, No Setters, No XML!
Lately I had a discussion about setters that have been introduced for the sake of testability. Just to be able to replace the actual collaborator with a stub or mock.
The discussion went something like this:
S: “We could use a dependency injection in conjunction with the @Inject
annotation with a container like Spring to avoid introducing protected setters for tests only.”
H: “But, isn’t that a lot of XML? Furthermore, you have to look in several places to get the overall picture of the test: The Java source code and the XML file.”
S: “There’s a very persistent myth that whenever you use Spring, you are doomed to live with almost as many XML files as code. By now it should be possible without a single line of XML…”
H: “Ok. Let’s give it a try!”
First we did some software archaeology[1] work …looking around in some source repositories, we found a plausible cause of how the myth came about. Once upon a time there was a Hero
with a setter introduced for the sole purpose of testing:
public class Hero {
private Sword sword;
public Hero() {
this(new DragonSlayer());
}
// for testing only
void setSword(Sword sword) {
this.sword = sword;
}
public void fight() {
this.sword.swing();
}
}
And, as expected, the XML file and
the actual test code:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "HeroTest-context.xml" })
public class HeroTests {
@Autowired
private Hero uut;
@Test
public void shouldSwingSwordWhenFighting() {
Sword sword = createMock(Sword.class);
uut.setSword(sword);
sword.swing();
replay(sword);
uut.fight();
verify(sword);
}
}
Ok. So far so good. But is this code necessary any more in times of the Java-based container configuration[2] available in Spring?
Let’s rewrite the Hero
to use dependency injection as defined in JSR 330[3]:
public class Hero {
@Inject
private Sword sword;
public void fight() {
this.sword.swing();
}
}
And convert the XML into its annotation-based counterpart
@Configuration
static class ContextConfiguration {
@Bean
public Sword sword() {
return createMock(Sword.class);
}
@Bean
public Hero uut() {
return new Hero();
}
}
Which can be easily integrated into the actual test class to have the configuration and the test in one place.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class HeroTests {
@Configuration
static class ContextConfiguration {...}
@Inject
private Hero uut;
@Inject
private Sword sword;
@Test
public void shouldSwingSwordWhenFighting() {
sword.swing();
replay(sword);
uut.fight();
verify(sword);
}
}
H: Wow. That’s all we need in one place and not a single line of XML!
S: I’d say this one is “Busted”.
--
[1] Software Archeology - https://en.wikipedia.org/wiki/Software_archaeology [2] Java-based container configuration - https://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-java [3] JSR-330 Dependency Injection for JavaTM - https://jcp.org/aboutJava/communityprocess/final/jsr330/
Image “Hero of Time” - from https://www.flickr.com/photos/alisdair/2104233/