- How to create a Spring 3, JPA 2.0, Maven project
- How to manage transactions in Spring
- and how to refactor your DAO classes to use entity managers from JEE (even if you are on Java SE)
Now, one thing that is missing in this picture is...
Testing DAO classes
There is a presentation by Rod Johnson of Spring on testing with Spring that explains you what unit testing and what integration testing is, what mock objects are and some of the best practices. The presentation itself is from 2007 and while the general ideas haven't changed, the implementation techniques have.
Therefore in this article I will show you how to integration test your DAO classes. I am going to use a JPA project for this, but the same test code will work for Hibernate as well.
Integration Testing principles
Integration testing is another level of tests; while unit tests test pieces of code in separation, integration tests test the bigger picture. Unit tests don't go to data sources, they are fast and simple. Integration tests hit the data sources and test multiple modules together.
So, you should have a database set up, in a well known state. This should not be the production database! But it should be identical (or as close as possible).
On this database you perform the tests. This is better than mock objects, because although you can simulate certain behaviors with mock objects, it's nothing compared to using a real database, with its triggers, views, stored procedures etc.
How does it, uhm, how does it work?
Integration testing that involves a data source, whether you use Spring or, say, DBUnit, often works according to the same schema.
You mess around with the database, add, update, remove. Then all the changes are reverted.
With Spring, they are rolled back.
Integration testing with Spring 3
Prior to Spring 3 the recommended way of testing JPA was to use AbstractJpaTests - but not anymore; now it's deprecated. The official documentation suggests you use (extend) AbstractJUnit38SpringContextTests. You would rather use AbstractJUnit4SpringContextTests if you use JUnit 4. But both strategies require extending and Java allows for inheritance only (which is good), so I will show you an alternative way of doing it.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "file:src/test/resources/applicationContext-test.xml" }) @TransactionConfiguration(transactionManager = "myTransactionManager", defaultRollback = true) @Transactional public class DogsDaoImplTest { // ...
Alright! Let's explain these annotations, one by one.
- @RunWith(SpringJUnit4ClassRunner.class) - this means that JUnit will enable the functionality of the Spring TextContext Framework. That makes it possible for you to use Spring goodies (annotations like @Autowired etc.) in the tests
- @ContextConfiguration - tells Spring where the beans files are. Note the format. This way it works within Eclipse, within Ant, within Maven, from Hudson etc.
- @TransactionConfiguration - here we configure the transaction. We specify the manager to use and if rollback should be the default thing to do when a transaction ends (a bit explicit - the default is true anyhow)
- @Transactional - all methods must run in transactions so that their effects can be rolled back
NOTE: Some configurations also use this annotation:
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class })But the code works without it... I don't see why it should be used. Perhaps someone can clarify on that.
And now a test method.
@Autowired private DogsDao dogsDaoImpl; @Test public void testPersistDog() { long dogsBefore = dogsDaoImpl.retrieveNumberOfDogs(); Dog dog = new Dog(); dog.setName("Fluffy"); dogsDaoImpl.persistDog(dog); long dogsAfter = dogsDaoImpl.retrieveNumberOfDogs(); assertEquals(dogsBefore + 1, dogsAfter); }
Dependencies
In the previous posts I screwed up dependencies - yes. I imported spring-dao, version 2.0.8 - WRONG. Here's the correct dependency set for the whole project:
<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>me.m1key</groupId> <artifactId>springtx</artifactId> <version>0.0.1-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.5.3-Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>3.5.3-Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.5.3-Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <version>1.0.0.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.5.3-Final</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.0.3.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.0.3.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>3.0.3.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.8</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.5.8</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.0.3.RELEASE</version> <type>jar</type> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> <type>jar</type> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.3.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.0.3.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> </dependencies> <repositories> <repository> <id>r.j.o-groups-public</id> <url>https://repository.jboss.org/nexus/content/groups/public/</url> </repository> </repositories> </project>
Previously I was getting this:
java.lang.NoSuchMethodError: org.springframework.transaction.interceptor. TransactionAttribute.getQualifier()Ljava/lang/String;Now it's fixed.
Other possibilities
Apart from JUnit traditional annotations, such as @Before and @BeforeClass, you can also use:
- @BeforeTransaction (org.springframework.test.context.transaction.BeforeTransaction)
- @AfterTransaction (org.springframework.test.context.transaction.AfterTransaction)
Summary
In this article I showed you how to integration test DAO classes with Spring 3.
Download source code for this article