Skip to content

Support rollbackFor attribute of @Transactional in the TestContext framework #23138

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

Closed
yanjuning opened this issue Jun 14, 2019 · 8 comments
Closed
Assignees
Labels
in: test Issues in the test module status: declined A suggestion or change that we don't feel we should currently apply status: feedback-provided Feedback has been provided type: enhancement A general enhancement

Comments

@yanjuning
Copy link

yanjuning commented Jun 14, 2019

Description

Transactional test methods wouldn't rollback for exception,when annotated by @Commit or @Rollback(false).

For example, the following roleService.saveOrUpdate(sysRole) would commit.

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional(rollbackFor = Exception.class)
@Commit
public class UserServiceTest {
    @Autowired
    private RoleService roleService;
    @Test
    public void testRollBack() {
        SysRole sysRole = new SysRole("General");
        roleService.saveOrUpdate(sysRole);
        throw new RuntimeException("ALWAYS_THROW");
    }
}

This would cause partial DML operation commit.

Suggestion

The following code snippet force commit or rollback according to flaggedForRollback

// class org.springframework.test.context.transaction.TransactionContext#endTransaction
if (this.flaggedForRollback) {
    this.transactionManager.rollback(this.transactionStatus);
} else {
    this.transactionManager.commit(this.transactionStatus);
}

I would like to make some changes, determine if there is a matching exception in the testContext.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 14, 2019
@sbrannen sbrannen added the in: test Issues in the test module label Jun 14, 2019
@sbrannen sbrannen changed the title spring-test transactional method should rollback for exception @Transactional test method should roll back for exception Jun 14, 2019
@sbrannen
Copy link
Member

That's an interesting use case. This has been the behavior of test-managed transactions since over a decade, though no one has ever raised this issue before.

In summary, you are asking for the TestContext framework to honor the rollbackFor attribute of @Transactional for exceptions thrown during test method execution.

Is that correct?

Note to self: the rollbackFor configuration is honored in production code in TransactionAspectSupport#completeTransactionAfterThrowing(...).

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jun 14, 2019
@yanjuning
Copy link
Author

Yes, it's correct.

The problem appears very accidentally. May I submit a pull request to make the SpringTest framework to honor the rollbackFor attribute of @Transactional?

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jun 14, 2019
@sbrannen
Copy link
Member

sbrannen commented Jun 14, 2019

Yes, it's correct.

Thanks for the confirmation.

The problem appears very accidentally. May I submit a pull request to make the SpringTest framework to honor the rollbackFor attribute of @Transactional?

Please hold off on submitting a PR until the team decides if we want to support this feature.

Tentatively slated for 5.2 RC1 for consideration.

@sbrannen sbrannen added this to the 5.2 RC1 milestone Jun 14, 2019
@sbrannen sbrannen self-assigned this Jun 14, 2019
@yanjuning
Copy link
Author

Got it, thanks,

@sbrannen sbrannen changed the title @Transactional test method should roll back for exception Support rollbackFor attribute of @Transactional in the TestContext framework Jun 18, 2019
@sbrannen
Copy link
Member

sbrannen commented Jun 18, 2019

Team Decision:

We have decided not to support the rollbackFor attribute of @Transactional in the TestContext framework.

Rationale: Spring's testing support has never honored the rollbackFor attribute of @Transactional. Doing so now would be a potentially breaking change for any code base that has that attribute set, since it has always been ignored.

Workaround: use the TestTransaction API instead. It is intended exactly for such power user cases. Examples will be posted separately.

In any case, the documentation can be improved with regard to which attributes in @Transactional are supported in tests. So I will leave this issue open in order to address that.

@sbrannen
Copy link
Member

To achieve the desired behavior, you need to introduce a try-catch block within your test method. Within the catch-block you need to invoke TestTransaction.flagForRollback() to signal to the testing framework that the current test-managed transaction should be rolled back instead of committed. In addition, you still need to throw the original exception in order to allow the test method to fail.

The following JUnit Jupiter based test class demonstrates how to do this.

  • serviceMethod() simulates a service that modifies the database and throws a business exception.
  • customRollbackWithTestTransactionApi() demonstates how to use the TestTransaction API directly.
  • customRollbackForApi() demonstrates that you can implement your own custom "rollback for" DSL to encapsulate the use of the TestTransaction API and the try-catch block.
  • afterTransaction() effectively asserts that the transaction was not committed.

When executing this test class, we see the following output to SYS_OUT.

Number of users in DB before transaction: 0
Number of users in DB after transaction: 0
Number of users in DB before transaction: 0
Number of users in DB after transaction: 0

The code is split into chunks to avoid the need to scroll.

@SpringJUnitConfig
@Transactional
@Commit
class CommittedTransactionalTests {

	@Autowired
	JdbcTemplate jdbcTemplate;
	@Test
	void customRollbackWithTestTransactionApi() throws Throwable {
		try {
			serviceMethod();
		}
		catch (Throwable t) {
			TestTransaction.flagForRollback();
			throw t;
		}
	}

	@Test
	void customRollbackForApi() throws Throwable {
		// rollbackFor(FileNotFoundException.class, () -> serviceMethod());
		rollbackFor(Exception.class, () -> serviceMethod());
	}
	@BeforeTransaction
	void beforeTransaction() {
		assertZeroUsers("before");
	}

	@AfterTransaction
	void afterTransaction() {
		assertZeroUsers("after");
	}

	void assertZeroUsers(String phase) {
		int numUsers = JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user");
		System.out.printf("Number of users in DB %s transaction: %d%n", phase, numUsers);
		assertThat(numUsers).isEqualTo(0);
	}
	static void rollbackFor(Class<? extends Throwable> exceptionType, Executable executable) throws Throwable {
		try {
			executable.execute();
		}
		catch (Throwable t) {
			if (exceptionType.isInstance(t)) {
				TestTransaction.flagForRollback();
			}
			throw t;
		}
	}

	@FunctionalInterface
	interface Executable {
		void execute() throws Throwable;
	}

	private void serviceMethod() throws Exception {
		this.jdbcTemplate.update("INSERT INTO user VALUES('Dilbert')");
		throw new Exception("Business Exception");
	}
	@Configuration
	static class Config {

		@Bean
		JdbcTemplate jdbcTemplate(DataSource dataSource) {
			return new JdbcTemplate(dataSource);
		}

		@Bean
		PlatformTransactionManager transactionManager(DataSource dataSource) {
			return new DataSourceTransactionManager(dataSource);
		}

		@Bean
		DataSource dataSource() {
			return new EmbeddedDatabaseBuilder()
				.generateUniqueName(true)
				.addScript("classpath:/test-schema.sql")
				.build();
		}
	}
}

@sbrannen sbrannen added the type: documentation A documentation task label Jun 18, 2019
@sbrannen sbrannen added type: enhancement A general enhancement and removed type: documentation A documentation task labels Jun 18, 2019
@sbrannen
Copy link
Member

Superseded by gh-23149

@sbrannen sbrannen added the status: declined A suggestion or change that we don't feel we should currently apply label Jun 18, 2019
@sbrannen sbrannen removed this from the 5.2 RC1 milestone Jun 18, 2019
@yanjuning
Copy link
Author

It works for me though the way to support rollback is not such straightforward.

Thanks for detailed instructions.

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 status: declined A suggestion or change that we don't feel we should currently apply status: feedback-provided Feedback has been provided type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants