Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

November Discount Launch 2022 – TEMP TOP (NPI)
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

1. Introduction

Spring has excellent support for declarative transaction management throughout application code as well as in integration tests.

However, we may occasionally need fine-grained control over transaction boundaries.

In this article, we'll see how to programmatically interact with automatic transactions set up by Spring in transactional tests.

2. Prerequisites

Let's assume that we have some integration tests in our Spring application.

Specifically, we're considering tests that interact with a database, for example, check that our persistence layer is behaving correctly.

Let's consider a standard test class – annotated as transactional:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { HibernateConf.class })
@Transactional
public class HibernateBootstrapIntegrationTest { ... }

In such a test, every test method is wrapped in a transaction, which gets rolled back when the method exits.

It's, of course, also possible to only annotate specific methods. Everything we'll discuss in this article applies to that scenario as well.

3. The TestTransaction Class

We'll spend the rest of the article discussing a single class: org.springframework.test.context.transaction.TestTransaction.

This is a utility class with a few static methods that we can use to interact with transactions in our tests.

Each method interacts with the only current transaction which is in place during the execution of a test method.

3.1. Checking the State of the Current Transaction

One thing we often do in tests is checking that things are in the state they are supposed to be.

Therefore, we might want to check whether there is a currently active transaction:

assertTrue(TestTransaction.isActive());

Or, we could be interested to check whether the current transaction is flagged for rollback or not:

assertTrue(TestTransaction.isFlaggedForRollback());

If it is, then Spring will roll it back just before it ends, either automatically or programmatically. Otherwise, it'll commit it just before closing it.

3.2. Flagging a Transaction for Commit or Rollback

We can change programmatically the policy to commit or to rollback the transaction before closing it:

TestTransaction.flagForCommit();
TestTransaction.flagForRollback();

Normally, transactions in tests are flagged for rollback when they start. However, if the method has a @Commit annotation, they start flagged for commit instead:

@Test
@Commit
public void testFlagForCommit() {
    assertFalse(TestTransaction.isFlaggedForRollback());
}

Note that these methods merely flag the transaction, as their names imply. That is, the transaction isn't committed or rolled back immediately, but only just before it ends.

3.3. Starting and Ending a Transaction

To commit or rollback a transaction, we either let the method exit, or we explicitly end it:

TestTransaction.end();

If later on, we want to interact with the database again, we have to start a new transaction:

TestTransaction.start();

Note that the new transaction will be flagged for rollback (or commit) as per the method's default. In other words, previous calls to flagFor… don't have any effect on new transactions.

4. Some Implementation Details

TestTransaction is nothing magical. We'll now look at its implementation to learn a little more about transactions in tests with Spring.

We can see that its few methods simply get access to the current transaction and encapsulate some of its functionality.

4.1. Where Does TestTransaction Get the Current Transaction From?

Let's go straight to the code:

TransactionContext transactionContext
  = TransactionContextHolder.getCurrentTransactionContext();

TransactionContextHolder is just a static wrapper around a ThreadLocal holding a TransactionContext.

4.2. Who Sets the Thread-Local Context?

If we look at who calls the setCurrentTransactionContext method, we'll find there's only one caller: TransactionalTestExecutionListener.beforeTestMethod.

TransactionalTestExecutionListener is the listener that Springs configures automatically on tests that are annotated @Transactional.

Note that TransactionContext doesn't hold a reference to any actual transaction; instead, it is merely a façade over the PlatformTransactionManager.

Yes, this code is heavily layered and abstract. Such are, often, the core parts of the Spring framework.

It's interesting to see how, under the complexity, Spring doesn't do any black magic – just a lot of necessary bookkeeping, plumbing, exception handling and so on.

5. Conclusions

In this quick tutorial, we've seen how to interact programmatically with transactions in Spring-based tests.

The implementation of all these examples can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Persistence footer banner
Comments are closed on this article!