JUnit, JPA and Spring: How to ensure that unit tests leave database clean on completion
Solution 1
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:
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.
Solution 2
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.
user497087
Updated on June 17, 2022Comments
-
user497087 almost 2 years
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 simply 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
-
OrangeDog about 13 years-1 as it has nothing to do with proxies and transactions do not propagate like that. +1 for pointing out that the dao layer should not be transactional.
-
Sean Patrick Floyd about 13 years@OrangeDog I have now seen that there is also a
@Transactional
attribute on the test method. I had only seen the@Transactional
annotations on the DAO methods. These annotations are nonsense, and I have pointed out why. -
Sean Patrick Floyd about 13 yearsThose transactional annotations will have no effect as they are internal method calls. Yes, as I have correctly pointed out.
-
OrangeDog about 13 years@Sean - Yes, but that's not part of the solution to this specific question.
-
Sean Patrick Floyd about 13 yearstrue. but you made it sound like I misunderstood the concept.