spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.6k stars 38.13k forks source link

Execute all test methods in a class within the same transaction [SPR-5520] #10191

Open spring-projects-issues opened 15 years ago

spring-projects-issues commented 15 years ago

Eric Jain opened SPR-5520 and commented

Being able to configure that all test methods in a class should be run in the same transaction would be useful for people using TestNG (which supports test method dependencies) to write functional tests.

This feature could also be useful in JUnit Jupiter when using @TestInstance(PER_CLASS) or the planned support for scenario tests.


Affects: 2.5.6

Issue Links:

1 votes, 1 watchers

spring-projects-issues commented 9 years ago

Sam Brannen commented

Since this issue has received 0 votes in the last 6 years, I am resolving it as "Won't Fix".

However, if the community deems this issue still relevant, this issue may potentially be reassessed at a later date.

spring-projects-issues commented 6 years ago

Sam Brannen commented

Reopening this issue in light of #21093 and JUnit Jupiter's upcoming support for scenario tests.

genzhewozou commented 4 years ago

哈哈哈,nb啊铁质

junichimiyazaki commented 3 years ago

Any progress? I found a workaround as below but I think this code is a bit hacky. So, I hope this feature be supported officially.

@Suppress("UnnecessaryAbstractClass", "EmptyFunctionBlock")
@ContextBootTest
@TestInstance(value = TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.SAME_THREAD)
abstract class AbstractTransactionalScenarioTest {

    @Autowired(required = true)
    lateinit var transactionManager: PlatformTransactionManager
    lateinit var status: TransactionStatus

    @BeforeAll
    private fun beforeTestClass() {
        val td = DefaultTransactionDefinition().apply {
            setName("ScenarioTestTx_${Thread.currentThread().id}")
            propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
        }
        status = transactionManager.getTransaction(td)
        beforeAll()
    }

    @AfterAll
    private fun afterTestClass() {
        afterAll()
        transactionManager.rollback(status)
    }

    protected fun beforeAll() {}
    protected fun afterAll() {}
}
class ScenarioTest : AbstractTransactionalScenarioTest() {

    override fun beforeAll() {
        // class wide init code. ex, db init
    }

    @Test
    @Transactional(propagation = Propagation.NESTED)
    fun testA() {
        // test 1...
    }

    @Test
    @Transactional(propagation = Propagation.NESTED)
    fun testB() {
        // test 2...
    }
}
ttddyy commented 3 years ago

Alternatively, you can create a custom TestExecutionListener that start/stop a transaction at the test class level.

Something like:

class MyTestExecutionListener extends AbstractTestExecutionListener {

  @Override
  public void beforeTestClass(TestContext testContext) throws Exception {
    PlatformTransactionManager tm = TestContextTransactionUtils.retrieveTransactionManager(testContext, null);
    TransactionDefinition txDef = new DefaultTransactionDefinition();
    TransactionStatus txStatus = tm.getTransaction(txDef);

    // put necessary context variables to threadlocal if parallel execution is needed
    ....
  }

  @Override
  public void afterTestClass(TestContext testContext) throws Exception {
    // retrieve necessary context values.
    ....

    tm.rollback(txStatus);
  }

}

Existing TransactionalTestExecutionListener is a good source for implementation details.

In addition, you can use JUnit5 @TestMethodOrder to ensure the testing order within the test class.

junichimiyazaki commented 3 years ago

@ttddyy Thank you for your reply! But sadly, I tried your annotation style solution but it didn't work properly. It seems rollback didn't work for each test case. (@Transactional(propagation = Propagation.NESTED) doesn't work?)

ttddyy commented 3 years ago

@junichimiyazaki I just wrote this sample impl and seems working to me. It starts and rollbacks transaction at class level. You can extend and add per method behavior if you want.

Just looking back your example, you want to add the NESTED propagation in each test method with rollback?? I'm not sure why you want to do it. Can it be simply start/rollback transaction on each method, which is exactly what TransactionalTestExecutionListener do by default.

junichimiyazaki commented 3 years ago

@ttddyy I'm sorry, I was misunderstanding it. Your case seems to fully satisfy the requirements of this ticket.

The reason what I added the NESTED propagation is that I want to add common DB initialization per class. Because it simplifies and speeds up each test cases.

GeenEva commented 3 years ago

I found this solution on StackOverflow and it saved the day:

Add the @DirtiesContext annotation, but provide it with the AFTER_EACH_TEST_METHOD classMode

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)