简体   繁体   中英

Disabling transaction on spring testng test method

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; 
    } 
 } 
}

Spring Boot >=2.1.5.RELEASE has the following solution.

By default, data JPA tests are transactional and roll back at the end of each test. See the relevant section in the Spring Framework Reference Documentation for more details. If that is not what you want, you can disable transaction management for a test or for the whole class as follows:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class ExampleNonTransactionalTests {

}

(s. Spring Doc )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM