Testing the layered arquitecture with Spring and TestNG

Following the post explaining a layered arquitecture with Spring and Hibernate, this entry will explain how to easily test its DAOs and Service components using Spring’s TestNG integration.

When it comes to isolating the environment for each layer, the main conceptual difference between the layers is this: the service layer is transactional on its own, so its methods can be tested without adding any extra components; but the DAO layer requires an ongoing transaction for its methods to work, so one must be supplied.

Preparing the environment

Two extra dependencies must be added in the project’s pom.xml file: spring test context framework, which has the helper classes for different test libraries, and the TestNG library, which is the framework that is going to be used. Both will be added with test scope, as they only have to be present in the classpath during that phase.

This is the relevant fragment of the pom.xml file:

<dependencies>
        [...]
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
            <classifier>jdk15</classifier>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.test</artifactId>
            <version>${springframework.version}</version>
            <scope>test</scope>
        </dependency>
	[...]
</dependencies>

Take care that you will need to define the properties holding the version values for the springframework and testng dependencies if you don’t have them already. In this entry, I assume the following versions:

<properties>
	[...]
	<springframework.version>3.0.0.RC2</springframework.version>
	<testng.version>5.10</testng.version>
	[...]
</properties>

Testing the DAO layer

So, DAO methods should be executed inside a transactional scope. To provide it, the test class will inherit from AbstractTransactionalTestNGSpringContextTests. As an extra, you may provide it with different spring application context configuration to adapt it to your test. Just be sure that in case you use a different configuration, it includes a TransactionManager so it can be used to create the transactions.

An example of a DAO test would be like this:

@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class UserDaoTest 
		extends AbstractTransactionalTestNGSpringContextTests {
 
	@Autowired
	private UserDao userDao;
 
	@Test
	@Rollback(true)
	public void simpleTest() {
		User user1 = this.userDao.findById(1l);
		assertNotNull(user1, "User 1 could not be retrieved.");
	}
}

The rollback annotation allows you to decide whether the supplied transaction should proceed or be rolled back at the end of the test. In this case it doesn’t matter as the test is of a read-only operation, but it’s very handy when testing write operations.

Testing the service layer

Testing the service layer is even easier as it doesn’t need any extra scope or configuration to work. Still, to get the extra value provided by the Spring Test Framework (selection of the spring configuration, context caching, dependency injection, etc.) it is a good idea to inherit from the AbstractTestNGSpringContextTests class.

An example of a service test would look like this:

@ContextConfiguration( locations={"classpath:applicationContext.xml"} )
public class UserServiceTest extends AbstractTestNGSpringContextTests {
 
	@Autowired
	private UserService userService;
 
	@Test
	public void simpleTest() {
		Collection<User> users = this.userService.getAllUsers();
		assertEquals(users.size(), 3,
			"Incorrect number of users retrieved.");
	}
}

Take note that to unit test the service layer, you should provide a mock DAO to the service so its operations are tested in isolation. Spring’s dependency injection comes again handy in this regard: as the DAO is injected into the service, it’s easy to adapt the test configuration so the service gets a mocked DAO instead. How to configure that may be explained in a following post.