JUnit, JPA and Spring: How to ensure that unit tests leave database clean on completion

13,114

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

Share:
13,114
user497087
Author by

user497087

Updated on June 17, 2022

Comments

  • user497087
    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
    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
    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
    Sean Patrick Floyd about 13 years
    Those transactional annotations will have no effect as they are internal method calls. Yes, as I have correctly pointed out.
  • OrangeDog
    OrangeDog about 13 years
    @Sean - Yes, but that's not part of the solution to this specific question.
  • Sean Patrick Floyd
    Sean Patrick Floyd about 13 years
    true. but you made it sound like I misunderstood the concept.