Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
spring-projects-issues opened this issue Feb 23, 2009 · 9 comments
Labels
in: test Issues in the test module type: enhancement A general enhancement

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Feb 23, 2009

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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

spring-projects-issues commented Mar 9, 2018

Sam Brannen commented

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

@genzhewozou

This comment was marked as off-topic.

@junichimiyazaki
Copy link

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
Copy link
Contributor

ttddyy commented Dec 20, 2020

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
Copy link

junichimiyazaki commented Dec 22, 2020

@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
Copy link
Contributor

ttddyy commented Dec 22, 2020

@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
Copy link

@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
Copy link

GeenEva commented Dec 31, 2020

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)

@sbrannen sbrannen modified the milestones: 6.x Backlog, General Backlog Mar 8, 2024
@sbrannen sbrannen removed their assignment Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

6 participants