Chapter 16. Testing

16.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.

16.2. Unit testing

One of the main benefits of Dependency Injection is that your code is much less likely to have any hidden dependencies on the runtime environment or other configuration subsystems. This allows for unit tests to be written in a manner such that the object under test can be simply instantiated with the new operator and have its dependences set in the unit test code. 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, i.e., 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.

16.3. Integration testing

However, it is also important to be able to perform some integration testing enabling you to test things such as:

  • The correct wiring of your Spring IoC container contexts.

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

The Spring Framework provides support for integration testing when using NUnit and Microsoft's Testing framework 'MSTest'. The NUnit classses are located in the assembly Spring.Testing.NUnit.dll and the MSTest is located in Spring.Testing.Microsoft.dll.

[Note]Note

The Spring.Testing.NUnit.dll library is compiled against NUnit 2.5.1. Note that test runners integrated inside VS.NET may or may not support this version. At the time of this writing Reshaper 4.5.0 did not properly support NUnit 2.5.1. To use Resharper with NUnit 2.5.1 you need to download 4.5.1 RC2 or later.

These namespaces provides NUnit and MSTest superclasses for integration testing using a Spring container.

These superclasses provide the following functionality:

16.3.1. Context management and caching

The Spring.Testing.NUnit and Spring.Testing.Microsoft namespace provides support for consistent loading of Spring contexts, and caching of loaded contexts. Similarly Spring.TestingSupport 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 NHibernate 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 property that subclasses must implement to provide the location of context definition files:

protected abstract string[] ConfigLocations { get; }

Implementations of this method must provide an array containing the IResource locations of XML configuration metadata used to configure the application. This will be the same, or nearly the same, as the list of configuration locations specified in App.config/Web.config or other deployment configuration.

By default, once loaded, the configuration file set 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 an object 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.

16.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 ConfigLocations property.

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 object 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 NUnit test class itself (we will look at the configuration immediately afterwards).

/// Using NUnit

[TestFixture]
public class HibernateTitleDaoTests : AbstractDependencyInjectionSpringContextTests  {

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

    // a setter method to enable DI of the 'titleDao' instance variable
    public HibernateTitleDao HibernateTitleDao {
        set { titleDao = value; }
    }

    [Test]
    public void LoadTitle() {
        Title title = this.titleDao.LoadTitle(10);
        Assert.IsNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected override string[] ConfigLocations {
        return new String[] { "assembly://MyAssembly/MyNamespace/daos.xml" };
    }

}

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

<?xml version="1.0" encoding="utf-8" ?>
<objects  xmlns="http://www.springframework.net">

    <!-- this object will be injected into the HibernateTitleDaoTests class -->
    <object id="titleDao" type="Spring.Samples.HibernateTitleDao, Spring.Samples">
        <property name="sessionFactory" ref="sessionFactory"/>
    </object>
    
    <object id="sessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate">
        <!-- dependencies elided for clarity -->
    </object>

</objects>

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

Using AbstractDependencyInjectionSpringContextTests with MSTest is very similar.

/// Using Microsoft's Testing Framework

[TestClass]
public class HibernateTitleDaoTests : AbstractDependencyInjectionSpringContextTests  {

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

    // a setter method to enable DI of the 'titleDao' instance variable
    public HibernateTitleDao HibernateTitleDao {
        set { titleDao = value; }
    }

    [Test]
    public void LoadTitle() {
        Title title = this.titleDao.LoadTitle(10);
        Assert.IsNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected override string[] ConfigLocations {
        return new String[] { "assembly://MyAssembly/MyNamespace/daos.xml" };
    }

}

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

16.3.2.1. Field level injection

If, for whatever reason, you don't fancy having setter properties 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).

[TestFixture]
public class HibernateTitleDaoTests : AbstractDependencyInjectionSpringContextTests{

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

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

    [TestMethod]
    public void LoadTitle() {
        Title title = this.titleDao.LoadTitle(10);
        Assert.IsNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected override string[] ConfigLocations {
        return new String[] { "assembly://MyAssembly/MyNamespace/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 object name in the configured Spring container.

16.3.3. Transaction management

One common issue in tests that access a real database is their effect 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 AbstractTransactionalDbProviderSpringContextTests 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 IPlatformTransactionManager object 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, AbstractTransactionalDbProviderSpringContextTests. This also requires that a DbProvider object definition - again, with any name - be present in the configurations. It creates an AdoTemplate 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 NUnit test suite.

16.3.4. Convenience variables

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

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

  • adoTemplate: inherited from AbstractTransactionalDbProviderSpringContextTests. 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 NHibernate's ISession interface.

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