Chapter 8. Testing

8.1. Introduction

The Spring team considers developer testing to be an absolutely integral part of enterprise software development. A thorough treatment of testing in the enterprise is beyond the scope of this chapter; rather, the focus here is on the value add that the adoption of the IoC principle can bring to unit testing; and on the benefits that the Spring Framework provides in integration testing.

8.2. Unit testing

One of the main benefits of Dependency Injection is that your code should really depend far less on the container than in traditional J2EE development. The POJOs that comprise your application should be testable in JUnit tests, with objects simply instantiated using the new operator, without Spring or any other container. You can use mock objects (in conjunction with many other valuable testing techniques) to test your code in isolation. If you follow the architecture recommendations around Spring you will find that the resulting clean layering and componentization of your codebase will naturally faciliate easier unit testing. For example, you will be able to test service layer objects by stubbing or mocking DAO interfaces, without any need to access persistent data while running unit tests.

True unit tests typically will run extremely quickly, as there is no runtime infrastructure to set up, whether application server, database, ORM tool, or whatever. Thus emphasizing true unit tests as part of your development methodology will boost your productivity. The upshot of this is that you do not need this section of the testing chapter to help you write effective unit tests for your IoC-based applications.

8.3. Integration testing

However, it is also important to be able to perform some integration testing without requiring deployment to your application server or connecting to other enterprise infrastructure. This will enable you to test things such as:

  • The correct wiring of your Spring IoC container contexts.

  • Data access using JDBC or an ORM tool. This would include such things such as the correctness of SQL statements / or Hibernate XML mapping files.

The Spring Framework provides first class support for integration testing in the form of the classes that are packaged in the spring-mock.jar library. Please note that these test classes are JUnit-specific.

The org.springframework.test package provides valuable JUnit TestCase superclasses for integration testing using a Spring container, while at the same time not being reliant on an application server or other deployed environment. They will be slower to run than unit tests, but much faster to run than the equivalent Cactus tests or remote tests relying on deployment to an application server.

These superclasses provide the following functionality:

8.3.1. Context management and caching

The org.springframework.test package provides support for consistent loading of Spring contexts, and caching of loaded contexts. Support for the caching of loaded contexts is important, because if you are working on a large project, startup time may become an issue - not because of the overhead of Spring itself, but because the objects instantiated by the Spring container will themselves take time to instantiate. For example, a project with 50-100 Hibernate mapping files might take 10-20 seconds to load the mapping files, and incurring that cost before running every single test case in every single test fixture will lead to slower overall test runs that could reduce productivity.

To address this issue, the AbstractDependencyInjectionSpringContextTests has an protected method that subclasses must implement to provide the location of context definition files:

protected String[] getConfigLocations();

Implementations of this method must provide an array containing the resource locations of XML configuration metadata - typically on the classpath - used to configure the application. This will be the same, or nearly the same, as the list of configuration locations specified in web.xml or other deployment configuration.

By default, once loaded, the configuration fileset will be reused for each test case. Thus the setup cost will be incurred only once (per test fixture), and subsequent test execution will be much faster. In the unlikely case that a test may 'dirty' the config location, requiring reloading - for example, by changing a bean definition or the state of an application object - you can call the setDirty() method on AbstractDependencyInjectionSpringContextTests to cause the test fixture to reload the configurations and rebuild the application context before executing the next test case.

8.3.2. Dependency Injection of test fixtures

When AbstractDependencyInjectionSpringContextTests (and subclasses) load your application context, they can optionally configure instances of your test classes by Setter Injection. All you need to do is to define instance variables and the corresponding setters. AbstractDependencyInjectionSpringContextTests will automatically locate the corresponding object in the set of configuration files specified in the getConfigLocations() method.

Consider the scenario where we have a class, HibernateTitleDao, that performs data access logic for say, the Title domain object. We want to write integration tests that test all of the following areas:

  • The Spring configuration; basically, is everything related to the configuration of the HibernateTitleDao bean correct and present?

  • The Hibernate mapping file configuration; is everything mapped correctly and are the correct lazy-loading settings in place?

  • The logic of the HibernateTitleDao; does the configured instance of this class perform as anticipated?

Let's look at the test class itself (we will look at the configuration immediately afterwards).

public final class HibernateTitleDaoTests extends AbstractDependencyInjectionSpringContextTests  {

    // this instance will be (automatically) dependency injected    
    private HibernateTitleDao titleDao;

    // a setter method to enable DI of the 'titleDao' instance variable
    public void setTitleDao(HibernateTitleDao titleDao) {
        this.titleDao = titleDao;
    }

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected String[] getConfigLocations() {
        return new String[] { "classpath:com/foo/daos.xml" };
    }

}

The file referenced by the getConfigLocations() method ('classpath:com/foo/daos.xml') looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN 2.0//EN"
    "http://www.springframework.org/dtd/http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>

    <!-- this bean will be injected into the HibernateTitleDaoTests class -->
    <bean id="titleDao" class="com.foo.dao.hibernate.HibernateTitleDao">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <!-- dependencies elided for clarity -->
    </bean>

</beans>

The AbstractDependencyInjectionSpringContextTests classes uses autowire by type. Thus if you have multiple bean definitions of the same type, you cannot rely on this approach for those particular beans. In that case, you can use the inherited applicationContext instance variable, and explicit lookup using (for example) an explicit call to applicationContext.getBean("titleDao").

If you don't want dependency injection applied to your test cases, simply don't declare any setters. Alternatively, you can extend the AbstractSpringContextTests - the root of the class hierarchy in the org.springframework.test package. It merely contains convenience methods to load Spring contexts, and performs no Dependency Injection of the test fixture.

8.3.2.1. Field level injection

If, for whatever reason, you don't fancy having setter methods in your test fixtures, Spring can (in this one case) inject dependencies into protected fields. Find below a reworking of the previous example to use field level injection (the Spring XML configuration does not need to change, merely the test fixture).

public final class HibernateTitleDaoTests extends AbstractDependencyInjectionSpringContextTests  {

    public HibernateTitleDaoTests() {
    	// switch on field level injection
        setPopulateProtectedVariables(true);
    }

    // this instance will be (automatically) dependency injected
    protected HibernateTitleDao titleDao;

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected String[] getConfigLocations() {
        return new String[] { "classpath:com/foo/daos.xml" };
    }

}

In the case of field injection, there is no autowiring going on: the name of your protected instances variable(s) are used as the lookup bean name in the configured Spring container.

8.3.3. Transaction management

One common issue in tests that access a real database is their affect on the state of the persistence store. Even when you're using a development database, changes to the state may affect future tests. Also, many operations - such as inserting to or modifying persistent data - cannot be done (or verified) outside a transaction.

The org.springframework.test.AbstractTransactionalDataSourceSpringContextTests superclass (and subclasses) exist to meet this need. By default, they create and roll back a transaction for each test. You simply write code that can assume the existence of a transaction. If you call transactionally proxied objects in your tests, they will behave correctly, according to their transactional semantics.

AbstractTransactionalSpringContextTests depends on a PlatformTransactionManager bean being defined in the application context. The name doesn't matter, due to the use of autowire by type.

Typically you will extend the subclass, AbstractTransactionalDataSourceSpringContextTests. This also requires that a DataSource bean definition - again, with any name - be present in the configurations. It creates a JdbcTemplate instance variable that is useful for convenient querying, and provides handy methods to delete the contents of selected tables (remember that the transaction will roll back by default, so this is safe to do).

If you want a transaction to commit - unusual, but occasionally useful when you want a particular test to populate the database - you can call the setComplete() method inherited from AbstractTransactionalSpringContextTests. This will cause the transaction to commit instead of roll back.

There is also convenient ability to end a transaction before the test case ends, through calling the endTransaction() method. This will roll back the transaction by default, and commit it only if setComplete() had previously been called. This functionality is useful if you want to test the behavior of 'disconnected' data objects, such as Hibernate-mapped objects that will be used in a web or remoting tier outside a transaction. Often, lazy loading errors are discovered only through UI testing; if you call endTransaction() you can ensure correct operation of the UI through your JUnit test suite.

8.3.4. Convenience variables

When you extend the AbstractTransactionalDataSourceSpringContextTests class you will have access to the following protected instance variables:

  • applicationContext (a ConfigurableApplicationContext): inherited from the AbstractDependencyInjectionSpringContextTests superclass. Use this to perfom explicit bean lookup, or test the state of the context as a whole.

  • jdbcTemplate: inherited from AbstractTransactionalDataSourceSpringContextTests. Useful for querying to confirm state. For example, you might query before and after testing application code that creates an object and persists it using an ORM tool, to verify that the data appears in the database. (Spring will ensure that the query runs in the scope of the same transaction.) You will need to tell your ORM tool to 'flush' its changes for this to work correctly, for example using the flush() method on Hibernate's Session interface.

Often you will provide an application-wide superclass for integration tests that provides further useful instance variables used in many tests.

8.3.5. Java 5+ specific support

If you are developing against Java 5 or greater, there are some additional annotations and support classes that you can use in your testing. The AbstractAnnotationAwareTransactionalTests class extends the AbstractTransactionalDataSourceSpringContextTests makes the text fixtures that you write that inherit from it aware of a number of (Spring-specific) annotations.

8.3.5.1. Annotations

The Spring Framework provides a number of annotations to help when writing integration tests. Please note that these annotations must be used in conjunction with the aforementioned AbstractAnnotationAwareTransactionalTests in order for the presence of these annotations to have any effect.

  • @DirtiesContext.

    The presence of this annotation on a text method indicates that the underlying Spring container is 'dirtied' during the execution of of the test method, and thus must be rebuilt after the test method finishes execution (regardless of whether the test passed or not). Has the same effect as a regular setDirty() invocation.

    @DirtiesContext
    public void testProcess() {
    	// some logic that results in the Spring container being dirtied
    }
  • @ExpectedException.

    Indicates that the annotated test method is expected to throw an exception during execution. The type of the expected exception is provided in the annotation, and if an an instance of the exception is thrown during the test method execution then the test passes. Likewise if an instance of the exception is not thrown during the test method execution then the test fails.

    @ExpectedException(SomeBusinessException.class)
    public void testProcessRainyDayScenario() {
    	// some logic that results in an Exception being thrown
    }
  • @NotTransactional.

    Simply indicates that the annotated test method must not execute in a transactional context.

    @NotTransactional
    public void testProcess() {
    	// ...
    }
  • @Repeat

    Indicates that the annotated test method must be executed repeatedly. The number of times that the test method is to be executed is specified in the annotation.

    @Repeat(10)
    public void testProcessRepeatedly() {
    	// ...
    }

8.3.6. PetClinic example

The PetClinic sample application included with the Spring distribution illustrates the use of these test superclasses. Most test functionality is included in the AbstractClinicTests, for which a partial listing is shown below:

public abstract class AbstractClinicTests
               extends AbstractTransactionalDataSourceSpringContextTests {

   protected Clinic clinic;

   public void setClinic(Clinic clinic) {
      this.clinic = clinic;
   }

   public void testGetVets() {
      Collection vets = this.clinic.getVets();
      assertEquals('JDBC query must show the same number of vets',
         jdbcTemplate.queryForInt('SELECT COUNT(0) FROM VETS'), 
         vets.size());
      Vet v1 = (Vet) EntityUtils.getById(vets, Vet.class, 2);
      assertEquals('Leary', v1.getLastName());
      assertEquals(1, v1.getNrOfSpecialties());
      assertEquals('radiology', ((Specialty) v1.getSpecialties().get(0)).getName());
      Vet v2 = (Vet) EntityUtils.getById(vets, Vet.class, 3);
      assertEquals('Douglas', v2.getLastName());
      assertEquals(2, v2.getNrOfSpecialties());
      assertEquals('dentistry', ((Specialty) v2.getSpecialties().get(0)).getName());
      assertEquals('surgery', ((Specialty) v2.getSpecialties().get(1)).getName());
}

Notes:

  • This test case extends the AbstractTransactionalDataSourceSpringContextTests class, from which it inherits Dependency Injection and transactional behavior.

  • The clinic instance variable - the application object being tested - is set by Dependency Injection through the setClinic(..) method.

  • The testGetVets() method illustrates how the inherited JdbcTemplate variable can be used to verify correct behavior of the application code being tested. This allows for stronger tests, and lessens dependency on the exact test data. For example, you can add additional rows in the database without breaking tests.

  • Like many integration tests using a database, most of the tests in AbstractClinicTests depend on a minimum amount of data already in the database before the test cases run. You might, however, choose to populate the database in your test cases also - again, within the one transaction.

The PetClinic application supports four data access technologies - JDBC, Hibernate, TopLink, and JPA. Thus the AbstractClinicTests class does not itself specify the context locations - this is deferred to subclasses, that implement the necessary protected abstract method from AbstractDependencyInjectionSpringContextTests.

For example, the Hibernate implementation of the PetClinic tests contains the following implementation:

public final class HibernateClinicTests extends AbstractClinicTests {

   protected String[] getConfigLocations() {
      return new String[] { 
         "/org/springframework/samples/petclinic/hibernate/applicationContext-hibernate.xml" 
      };
   }
}

As the PetClinic is a very simple application, there is only one Spring configuration file. Of course, more complex applications will typically break their Spring configuration across multiple files. Instead of being defined in a leaf class, config locations will often be specified in a common base class for all application-specific integration tests. This may also add useful instance variables - populated by Dependency Injection, naturally - such as a HibernateTemplate, in the case of an application using Hibernate.

As far as possible, you should have exactly the same Spring configuration files in your integration tests as in the deployed environment. One likely point of difference concerns database connection pooling and transaction infrastructure. If you are deploying to a full-blown application server, you will probably use its connection pool (available through JNDI) and JTA implementation. Thus in production you will use a JndiObjectFactoryBean for the DataSource, and JtaTransactionManager. JNDI and JTA will not be available in out-of-container integration tests, so you should use a combination like the Commons DBCP BasicDataSource and DataSourceTransactionManager or HibernateTransactionManager for them. You can factor out this variant behavior into a single XML file, having the choice between application server and 'local' configuration separated from all other configuration, which will not vary between the test and production environments.

8.4. Further Resources

This section contains links to further resources about testing in general.