Filed under: Java, TDD, — Tags: Dependency injection, Inversion of Control, IoC, JUnit, POJO, Spring — Thomas Sundberg — 2010-05-16
Dependency injection is invaluable when building software. Decoupling components means that it is possible to test them independently.
A popular tool for dependency injection is Spring. Spring may, however, be overwhelming; it is a large toolbox that may be used for a variety of different purposes.
I will show a minimal configuration that may be used to test a Java class created as a bean in a JUnit test case. We will only look at how to get the testing up and running.
Building software today may be done using very powerful building tools. One popular, and powerful tool, is Maven. Maven will assist you to download the needed dependencies, to compile your code, to run tests and to package the result in a jar.
A directory layout:
root -- src -- main -- java -- se -- sigma -- experinemtal -- Pojo.java | +- test -- main -- java -- se -- sigma -- experinemtal -- PojoTest.java | +- resources -- applicationContextTest.xml | + pom.xml
This is a standard Maven directory layout. We will be working with four files:
Let’s start with the Maven project definition file, pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>se.sigma.experimental</groupId> <artifactId>spring-maven</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>Smallest possible Spring and Maven example</name> <url>http://www.sigma.se</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>2.5.6</version> </dependency> </dependencies> </project>
We have a header defining the project and we have a dependency part that defines the dependencies that we need for this example.
The header tells us a little bit about the project, the name of the artifact created and the format.
The dependency part is the interesting part. Here we define three dependencies.
This is where the interesting parts will happen today. We have a test class that only verifies that an injected class is set.
package se.sigma.experimental; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static junit.framework.Assert.assertNotNull; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:/applicationContextTest.xml" }) public class PojoTest { @Autowired private Pojo pojo; @Test public void verifyAutowiring() { assertNotNull("Expected pojo to be set", pojo); } }
The interesting magic is the test runner @RunWith(SpringJUnit4ClassRunner.class) annotation and the @Autowired annotation.
If we run the test with the Spring JUnit runner, it will parse the applicationContext specified with the @ContextConfiguration annotation. The only thing that we want to make sure is that the applicationContextTest.xml is available in the classpath. It is stored in ./src/test/resources so Maven will automatically add it to the test classpath.
The @Autowired annotation tells us that we are expecting someone to inject an instance of se.sigma.experimental.Pojo in some magic way. It will be injected by Spring when we run the test, since we are using the SpringJUnit4ClassRunner to run our test.
The only thing missing now is the connection between the Production code and the Test code. It is defined in the applicationContextTest.xml file.
To be able to test any production code, we need some code. A POJO (Plain Old Java Object) will be sufficient in this example.
The simplest possible POJO may be:
package se.sigma.experimental; public class Pojo { }
It doesn't do anything. We have a minimum of stuff that can fail.
The glue needed to connect our test with our production code is provided in applicationContextTest.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean id="pojo" class="se.sigma.experimental.Pojo"/> </beans>
This is a small configuration, almost nothing that can go wrong. All we do is define a bean called pojo. This bean is based on the class se.sigma.experimental.Pojo. Instances of it will be injected into all classes that need it. Classes may specify that they need our Pojo bean either by using the @Autowired annotation or by defining them as beans in the application context.
Finally, we want to build and run our test so we can verify that it really works.
mvn clean install
Maven will download the dependencies it needs, compile, run the test and finally package the result.
Dependency injection is a powerful tool to break dependencies. Without a too tight coupling between all classes in the system we will be able to test the in smaller pieces and inject mock or fake object where we need. This will allow us to build and test our system using tools like Test Driven Development, TDD.
Spring is magic, but not so magic.