Need explanation on the necessity of a prior flushing to avoid false positives whenTesting with Spring ?

In the spring documentation regarding testing, it states:

Avoid false positives when testing ORM code

When you test code involving an ORM framework such as JPA or Hibernate, flush the underlying session within test methods which update the state of the session. Failing to flush the ORM framework's underlying session can produce false positives: your test may pass, but the same code throws an exception in a live, production environment. In the following Hibernate-based example test case, one method demonstrates a false positive and the other method correctly exposes the results of flushing the session.

Can someone explain why i need to call flush?

Answers


Well, you actually skipped the interesting part, the example :) Here it is:

// ...

@Autowired
private SessionFactory sessionFactory;

@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the session is
    // finally flushed (i.e., in production code)
}

@Test(expected = GenericJDBCException.class)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

// ...

What this example tries to illustrate is that unless you actually flush the session (A.K.A. the first level cache) to sync in-memory changes with the database, you're not really testing the database integration and might not test the real expected behavior or miss a problem.

For example, the database could return an error because of, say a constraint violation, and if you don't hit the database, you won't exhibit this right behavior, as in the falsePositive() test method above. This test method should fail, or expect an exception but will just pass. On the other hand, the other test method with the flush does test the real behavior. Hence the need to flush.


Spring documentation uses the wrong concept. It has been clear

but the same code throws an exception in a live, production environment

Here goes wikipedia

Type II error, also known as an "error of the second kind", a β error, or a "false negative": the error of failing to reject a null hypothesis when it is in fact not true. An example of this would be if a test shows that a woman is not pregnant, when in reality, she is.

If you see the sample provided by Spring, The production environment throws an exception (A GenericJDBCException), but it has not been detected. In order to see, you must call the underlying commit when using some ORM provider.

XUnit Test patterns definition

A situantion in which a test passes even though the system under test is not working properly.

So the right concept is falseNegative

@Test // no expected exception!
public void falseNegative() {

Annotating Spring tests with @Transactional is convenient but it's not how your production code will be executed. The @Transactional annotation will start a transaction prior to running your test method and it will roll it back when the test method finishes.

While commit is preceded by a flush, the roll-back is not, so a manual flush is a safety-mechanism to ensure all Entity changes are translated to SQL statements.

A more appropriate design would be to draw the transaction boundaries explicitly like this:

@Test
public void testRootObjects() {

    final Company newCompany = new Company();
    newCompany.setName("TV Company");

    final Long companyId = transactionTemplate.execute(new TransactionCallback<Long>() {
        @Override
        public Long doInTransaction(TransactionStatus transactionStatus) {
            entityManager.persist(newCompany);
            return newCompany.getId();
        }
    });
    Company detachedCompany = transactionTemplate.execute(new TransactionCallback<Company>() {
        @Override
        public Company doInTransaction(TransactionStatus transactionStatus) {
            Company attachedCompany = entityManager.find(Company.class, companyId);
            assertEquals(newCompany, attachedCompany);
            assertEquals(newCompany.hashCode(), attachedCompany.hashCode());
            return attachedCompany;
        }
    });
    assertEquals(newCompany, detachedCompany);
    assertEquals(newCompany.hashCode(), detachedCompany.hashCode());
}

The TransactionTemplate will commit your code so there's no need for manual flushes.

If you call @Transactional service methods through their interface, you won't need the transactionTemplate at all, since you are calling a Spring proxy which will call TransactionInterceptor (assuming you instructed Spring to be aware of transaction annotations: ) and therefore transactions will be started/committed on your behalf.


Does anyone checked with the annotation @TransactionConfiguration? If you are using @Transactional annotation driven in your project, you can simply set @TransactionConfiguration(defaultRollback = false, transactionManager = "YourTransactionManager") in your test case will work perfectly, hopefully this will helps you.


Need Your Help

RabbitMQ HelloWorld messages being blocked on localhost?

java message rabbitmq mq

I have been running the hello world RabbitMQ example with the code below: