10

All MyService methods are transactional. The junit test below, gets count of items, saves a new item, and gets count of items to make sure that counts has been incremented by 1.

public class MyTest extends ServiceTest{

    1. int countBefore = myService.getCount();    //return n
    2. myService.add(item);                       //item is really added to DB
    3. int countAfter  = myService.getCount();    //return n (sometimes n+1)
}

@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED)
getCount(){…}

@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.SERIALIZABLE)
add(){…}

@Ignore
@ContextConfiguration(locations = { "file:src/main/resources/xxx-context.xml", 
                                    "file:src/main/resources/xxx-data.xml", 
                                    "file:src/main/resources/xxx-services.xml"  })
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)
@TestExecutionListeners( {  DependencyInjectionTestExecutionListener.class,
                            DirtiesContextTestExecutionListener.class,
                            TransactionalTestExecutionListener.class,
                            TestListener.class})

public class ServiceTest extends AbstractUT{

@Ignore
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners( {TestListener.class})
public class AbstractUT{

When debugging (3.) returns n+1 which is what I want. But when running the test without debug I get n.

Even sometimes when running the test I get n+1 and next time I get n and when comparing the std output between the two execution, it looks exactly the same. I have enabled log4j.logger.org.springframework.transaction=TRACE and I can see:

Initializing transaction synchronization
Getting transaction for MyService.getCount
...
Completing transaction for MyService.getCount
Clearing transaction synchronization

...

Initializing transaction synchronization
Getting transaction for MyService.add
...
Completing transaction for MyService.add
Clearing transaction synchronization

...

Initializing transaction synchronization
Getting transaction for MyService.getCount
...
Completing transaction for MyService.getCount
Clearing transaction synchronization

So transactions are being executed one after the other, but how is possible that (3.) don't see the saved item?

Transaction managment is setup in my test class as per: https://stackoverflow.com/a/28657650/353985

How can I find what is going wrong? Thanks!

Community
  • 1
  • 1
redochka
  • 12,345
  • 14
  • 66
  • 79
  • Do you use `` ? anyway see my answer on [this link](http://stackoverflow.com/a/25910635/3364187) . – Xstian Feb 23 '15 at 08:27
  • Can you add the configuration and your TestClass ? – Xstian Feb 23 '15 at 08:42
  • @Xstian i added conf and test classes. thx – redochka Feb 23 '15 at 08:48
  • It is a weird behavior .. the configuration seems to work well. The only thought is that the `item` have not been added, but have been updated. It is possible? the add method perform only an `insert` or `update` too? – Xstian Feb 23 '15 at 09:15
  • item is being added then updated in the same tx. Could this be the issue? – redochka Feb 23 '15 at 09:22
  • I think so. Probably, the `iten` update another record then `n` does not become `n+1`. It is only a supposition but you could check using jdbc `@Before` and `@After` the unit test. – Xstian Feb 23 '15 at 09:33
  • @Xstian don't agree with you on this because when test finishes I check the DB and item is always saved. So it should be always n+1 – redochka Feb 23 '15 at 09:54

4 Answers4

5

Had similar issue, but in my case it did not rollback. It seems that you forgot to add @Transactional. From documentation (link)

Transaction management

In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener which is configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded via @ContextConfiguration semantics (further details are provided below). In addition, you must declare Spring’s @Transactional annotation either at the class or method level for your tests.

Here is example form the link above.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
@Transactional
public class FictitiousTransactionalTest {

@BeforeTransaction
public void verifyInitialDatabaseState() {
    // logic to verify the initial state before a transaction is started
}

@Before
public void setUpTestDataWithinTransaction() {
    // set up test data within the transaction
}

@Test
// overrides the class-level defaultRollback setting
@Rollback(true)
public void modifyDatabaseWithinTransaction() {
    // logic which uses the test data and modifies database state
}

@After
public void tearDownWithinTransaction() {
    // execute "tear down" logic within the transaction
}

@AfterTransaction
public void verifyFinalDatabaseState() {
    // logic to verify the final state after transaction has rolled back
}

}

kyla
  • 396
  • 1
  • 8
  • I did notice you said that your methods have @Transactional, but I still had to add them on my method levels for my tests. – kyla Feb 26 '15 at 21:48
  • Even by adding the @Transactional on the test method I'm getting the same behavior. – redochka Feb 27 '15 at 01:15
  • what is your propagation on your transactions? – kyla Feb 27 '15 at 01:40
  • @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.SERIALIZABLE i have also updated the question with this info – redochka Feb 27 '15 at 08:22
0

A solution I found till now to pass test is to put assert in afterTransaction method

public class MyTest extends ServiceTest{

@Test
public void test(){
    1. int countBefore = myService.getCount();    //return n
    2. myService.add(item);                       //item is really added to DB
}
@AfterTransaction
public void verifyFinalDatabaseState() {
    3. int countAfter  = myService.getCount();    //return n (sometimes n+1) 
                                                  //Now always return n+1
}
redochka
  • 12,345
  • 14
  • 66
  • 79
0

Because the current running transaction is set at the test method level, you have two options:

  1. You either remove the @Transactional from the test method and rely on your service method @Transactional boundaries. This way when you call:

    int countBefore = myService.getCount();
    myService.add(item);
    int countAfter  = myService.getCount();
    

Each service call will run in an isolated transaction, just like it happens in the run-time production call.

  1. You flush the Hibernate Session, just after adding the item:

    int countBefore = myService.getCount();
    myService.add(item);
    transactionTemplate.execute(new TransactionCallback<Void>() {
        @Override
        public Company doInTransaction(TransactionStatus transactionStatus) {
            entityManager.flush();
            return null;
        }
    });
    int countAfter  = myService.getCount();
    

A HQL/JPQL count query should trigger a flush in AUTO flush mode, but an SQL native query doesn't flush the Session.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Already tried both options of your answer. 1- My test is not @ transactional, 2- I don't have entityManager, I am using sessionFactory.getCurrentSession.flush() Still getting the same odd behaviour. The only thing that works till now is assert in @ AfterTransaction. See my answer. – redochka Mar 03 '15 at 12:07
  • You can use the Hibernate Session as well. Try with `transactionTemplate` as I suggested. – Vlad Mihalcea Mar 03 '15 at 12:35
0

I would have asked this in a comment but since my reputation does not allow it, I would just try to provide an answer.

It is possible that your are using and ORM that caches the results of count query. Depending how your add/getCount methods are implemented and the configurations of the ORM and datasource, on your second invocation of getCount, you might get a cached value obtained during first invocation of getCount.

This does not explain however why in debug mode you always get the correct result.

Vladimir G.
  • 418
  • 3
  • 10
  • I'm using hibernate, and your idea comes to me too. How to get around the hibernate cache? – redochka Mar 05 '15 at 16:24
  • have a look here [Hibernate Configuration](https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/session-configuration.html) Table 3.5. I believe hibernate.cache.use_query_cache and hibernate.cache.use_second_level_cache would be of interest to you. Try setting both to false in your hibernate configuration. – Vladimir G. Mar 09 '15 at 20:36