Background
Spring provides TestExecutionListener
interface that we can implement to hook into the test execution lifecycle. This helps us in ensuring that our test classes are concise and not cluttered with test data preparation and clean up code.
In this article, we will look at how we can use TestExecutionListener
to manage test data. We will implement listeners to create initial data, update relevant data, and clean up data after the test execution.
TestExecutionListener
classes
Creating initial data
We will start by creating initial User data which consists of name
and username
fields.
class UserCreationTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) {
var mongo = testContext.getApplicationContext().getBean(MongoOperations.class);
mongo.save(new User("Rashidi Zin", "rashidi.zin"));
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
By default getOrder
method returns LOWEST_PRECEDENCE
which means that this listener will be executed last. Since we want this listener to always be executed first, we will set the order to HIGHEST_PRECEDENCE
.
Update relevant data
Our test will focus on finding User
with status
INACTIVE
. We will create a listener to update the status
of the User
to INACTIVE
after the test execution.
class UserStatusUpdateTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) {
var mongo = testContext.getApplicationContext().getBean(MongoOperations.class);
var findByUsername = mongo.findOne(query(where("username").is("rashidi.zin")), User.class);
mongo.save(findByUsername.status(INACTIVE));
}
@Override
public int getOrder() {
return 1;
}
}
Given that we are expecting a User
with username
rashidi.zin
to be returned, we will update the status
of the User
with username
rashidi.zin
to INACTIVE
.
In this listener, we will set the order to 1
as we want to ensure it will not be the last one to be executed.
Clean up data
Both listeners above will create and update User
data. They will be executed before test class. For data cleanup it will be executed after test class is executed.
class UserDeletionTestExecutionListener extends AbstractTestExecutionListener {
private static Logger log = LoggerFactory.getLogger(UserDeletionTestExecutionListener.class);
@Override
public void afterTestClass(TestContext testContext) {
var mongo = testContext.getApplicationContext().getBean(MongoOperations.class);
mongo.dropCollection(User.class);
log.info("user collection dropped");
}
}
Registering TestExecutionListener
classes
Finally, we will implement a test class to test the UserRepository which will be executed with the listeners above.
We will define necessary TestExecutionListeners
using @TestExecutionListeners
annotation and we will also set the mergeMode
to MERGE_WITH_DEFAULTS
to ensure that the default listeners are also executed.
@Testcontainers
@DataMongoTest
@TestExecutionListeners(
listeners = { UserCreationTestExecutionListener.class, UserStatusUpdateTestExecutionListener.class, UserDeletionTestExecutionListener.class },
mergeMode = MERGE_WITH_DEFAULTS
)
class UserRepositoryTests {
@Container
@ServiceConnection
private static final MongoDBContainer mongo = new MongoDBContainer("mongo:latest");
@Autowired
private UserRepository repository;
@Test
@DisplayName("Given there are users with status INACTIVE, When I search for users with status INACTIVE, Then I should get users with status INACTIVE")
void findByStatus() {
var inactiveUsers = repository.findByStatus(INACTIVE);
assertThat(inactiveUsers)
.hasSize(1)
.extracting("username")
.containsOnly("rashidi.zin");
}
}
While findByStatus
will validate our implementation in UserCreationTestExecutionListener
and UserStatusUpdateTestExecutionListener
, a log message will be printed to indicate that UserDeletionTestExecutionListener
is executed.
Conclusion
With TestExecutionListener
data can be reused across test classes. This helps us in ensuring that our test classes are concise and not cluttered with test data preparation and clean up code.