问题描述:

I am using TestNG together with Spring and JPA. So far I am using to test database stuff my test class which extends AbstractTransactionalTestNGSpringContextTests. With @TransactionConfiguration(defaultRollback = true) everything works fine and I do not need to cary about cleanup. Spring creates a default transaction on the beginning of each of my test method which is than rollbacked. This is a very neat trick to solve famous "Transactional tests considered harmful" problem.

Unfortunately I need for one method in this class (one test) to not have this default transaction. This is because this test method simulates batch processing and I have multiple transactions inside it which are in production independent. The only way I was able to simulate and solve problem is to configure those inner transactions with Propagation.REQUIRES_NEW but this I do not want to have in production code. Is there some way to disable Spring transaction for my particular test method (so I do not need to use Propagation.REQUIRES_NEW but Propagation.REQUIRED in my service methods) ?

网友答案:

I know you've already solved your problem, but for folks who will come here in the future...

Unfortunately it seems that there is no way to disable existing transaction inside test that uses @Transactional annotation.

IMHO Spring approach here is extremely inflexible. But there is a workaround for your problem. It would be enough to encapsulate needed logic inside Spring TransactionTemplate class. This would ensure that code in your test case would be launched inside a new transaction.


Personal advice: the best and the most flexible way from my point of view is to abandon from the very beginning @Transactional tests and setup database into known state before every test. In this way, Spring will manage transactions exactly the same way as in production.
No quirks, no hacks, no manual transaction management.

I know that using @Transactional with a "rollback" policy around unit tests is a tempting idea, but it has too many pitfalls. I recommend reading this article Spring Pitfalls: Transactional tests considered harmful.

Of course I don't complain here about @Transactional itself - because it greatly simplifies transaction management in the production code.

网友答案:

I have found that by executing the body of my test in separate thread prevent Spring from transaction. So the workaround solution is something like:

    @ContextConfiguration(classes = { test.SpringTestConfigurator.class })
    @TransactionConfiguration(defaultRollback = false)
    @Slf4j
    @WebAppConfiguration
    public class DBDataTest extends AbstractTransactionalTestNGSpringContextTests {    
    /**
     * Variable to determine if some running thread has failed.
     */
    private volatile Exception threadException = null;

   @Test(enabled = true)
    public void myTest() {
        try {
            this.threadException = null;
            Runnable task = () -> {
                myTestBody();
            };
            ExecutorService executor = Executors.newFixedThreadPool(1);
            executor.submit(task);
            executor.shutdown();
            while (!executor.isTerminated()) {
                if (this.threadException != null) {
                    throw this.threadException;
                }
            }
            if (this.threadException != null) {
                throw this.threadException;
            }
        } catch (Exception e) {
            log.error("Test has failed.", e);
            Assert.fail();
        }
    }

 public void myTestBody() {
    try {
        // test body to do
    }
    catch (Exception e) {
       this.threadException = e; 
    } 
 } 
}
相关阅读:
Top