JUnit, JPA and Spring: How to ensure that unit tests leave database clean on completion
I'm trying to use SpringJunit4ClassRunner
to test my DAO classes without leaving data behind when I've finished, through the use of the @Transactional
annotation. My DAO class contains (stripped down):
@Repository
public class IdsFunctionJpaController {
@PersistenceContext
EntityManager em;
public void save(IdsFunction function) {
if (function.getId() == 0) {
create(function);
} else {
update(function);
}
}
@Transactional
private void create(IdsFunction idsFunction) {
try {
em.persist(idsFunction);
}
catch (Exception e) {
System.out.println(e);
} finally {
em.close();
}
}
@Transactional
private void update(IdsFunction function) {
try {
em.merge(function);
} finally {
em.close();
}
}
}
and my starting JUnit test case is
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/applicationContext.xml"} )
public class IdsFunctionJpaControllerTest {
@Autowired
IdsFunctionJpaController dao;
@Test
@Transactional
public void addFunction() {
IdsFunction function = new IdsFunction();
function.setDescription("Test Function Description");
dao.save(function);
assertTrue(function.getId() != 0);
}
}
What I'm trying to do here is s开发者_JS百科imply test that the entity has been created, but this test fails. If I remove the @Transactional
annotation, then the test passes, but the test entity remains in the database. What am I doing wrong?
Regards
Proxying mechanisms
You are banging your head against JDK proxies.
Your dao.save()
method is non-transactional, and it tries to call the transactional methods create()
and update()
. But the transactional stuff is happening in a JDK proxy outside the class, while the save method is already in the class.
See this previous answer of mine for reference.
Solutions:
- make your
save()
method transactional - (much better) don't make your DAOs transactional at all. Transactions belong in the service layer, not the DAO layer.
Reference:
- Understanding AOP Proxies
- Declarative Transaction Management
Update: I was misguided by the presence of the confusing @Transactional
annotations on the Dao methods. You should delete them, they do nothing and confuse people.
As you can read in the links I posted above, @Transactional
annotations only have effect when they are present on public Methods that will be called from outside the Spring bean (so you can't have one method in a class that delegates to one or more proxied methods of the same class).
Transactional Tests
Spring provides special support classes for transactional testing, as outlined in 9.3.5.4 Transaction management . If you let your test class extend from AbstractTransactionalJUnit4SpringContextTests
you get an automatic transaction rollback after every test. In most cases that is exactly what you need.
You need to flush the session. Ordinarily this happens at the end of a transaction, which is why you usually only have to worry about it in tests.
Inject the EntityManager
into your test class and call em.flush()
after the save.
Also, your DAO layer shouldn't be transactional. Transactions typically only make sense in the service layer.
Edit:
In fact, your DAO is also completely wrong, which this test won't be able to show. Those transactional annotations will have no effect as they are internal method calls. You should also never close the EntityManager
yourself - the container (Spring) will do this for you. Also, don't catch generic Exception
s and when you do don't just log and ignore them. Exceptions should be propagating to the service layer where they should be handled properly. Also, don't print to stdout, use a proper logging framework.
精彩评论