Dependency injection

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.

Project structure

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:

Maven project definition

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.

The test code

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.

The production code

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.

Spring configuration

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.

Build and test

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.

Conclusion

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.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS