Problem
The integration test suite currently takes 25–30 minutes to run. The main bottlenecks are:
-
Excessive @DirtiesContext usage
There are approximately 60 @DirtiesContext(methodMode = AFTER_METHOD) annotations across 14 test files.
Each annotation destroys and recreates the full Spring application context, which takes roughly 5–10 seconds per recreation due to bean wiring, RSA key generation, and DataInitializer execution.
-
Per-class Testcontainers setup
Each integration test class declares its own @Container MySQL instance.
Because each container produces different connection properties, Spring cannot effectively reuse cached application contexts. This results in approximately 17 separate Spring context creations.
-
Repeated DataInitializer execution
DataInitializer contains about 1,193 lines of seed data and runs every time a new Spring context is created.
It currently runs approximately 78 times total:
- ~17 initial context creations
- ~60 additional recreations caused by
@DirtiesContext
Proposed Solution
1. Use a singleton container pattern
Create a SharedContainers utility class that starts MySQL and Mailpit containers once per JVM.
All integration test classes should reference these shared containers instead of declaring their own per-class containers.
2. Introduce an abstract integration test base class
Create an AbstractIntegrationTest base class with @Transactional.
All integration tests should extend this class. Spring Test will roll back the transaction after each test method, restoring the database to the original DataInitializer-seeded state.
This allows us to remove @DirtiesContext from most or all integration tests.
This approach is valid because:
- Tests use
webEnvironment = MOCK with MockMvc
- Test execution and controller/service execution occur in the same thread
- The test transaction propagates correctly
- Services do not use
REQUIRES_NEW
DataInitializer runs during context startup and its committed data survives test rollback
3. Migrate all integration test classes
Update all 17 integration test classes to:
- Extend
AbstractIntegrationTest
- Remove per-class container declarations
- Remove
@Testcontainers where no longer needed
- Remove redundant Spring test annotations
- Remove all unnecessary
@DirtiesContext annotations
Expected Impact
| Metric |
Before |
After |
| MySQL containers started |
~17 |
1 |
| Spring contexts created |
~77 |
~2 |
DataInitializer executions |
~78 |
~2 |
| Estimated total runtime |
25–30 min |
~5 min |
Accepted Tradeoff
Using @Transactional on integration tests keeps the Hibernate session open for the duration of each test.
As a result, these tests may no longer detect LazyInitializationException issues that would occur outside a transaction.
This is acceptable for this test suite because these integration tests primarily verify API contracts through HTTP request/response behavior. They are not intended to validate transaction boundary behavior.
If needed, a small number of targeted non-transactional tests can be added separately to verify lazy-loading or transaction-boundary behavior.
Problem
The integration test suite currently takes 25–30 minutes to run. The main bottlenecks are:
Excessive
@DirtiesContextusageThere are approximately 60
@DirtiesContext(methodMode = AFTER_METHOD)annotations across 14 test files.Each annotation destroys and recreates the full Spring application context, which takes roughly 5–10 seconds per recreation due to bean wiring, RSA key generation, and
DataInitializerexecution.Per-class Testcontainers setup
Each integration test class declares its own
@ContainerMySQL instance.Because each container produces different connection properties, Spring cannot effectively reuse cached application contexts. This results in approximately 17 separate Spring context creations.
Repeated
DataInitializerexecutionDataInitializercontains about 1,193 lines of seed data and runs every time a new Spring context is created.It currently runs approximately 78 times total:
@DirtiesContextProposed Solution
1. Use a singleton container pattern
Create a
SharedContainersutility class that starts MySQL and Mailpit containers once per JVM.All integration test classes should reference these shared containers instead of declaring their own per-class containers.
2. Introduce an abstract integration test base class
Create an
AbstractIntegrationTestbase class with@Transactional.All integration tests should extend this class. Spring Test will roll back the transaction after each test method, restoring the database to the original
DataInitializer-seeded state.This allows us to remove
@DirtiesContextfrom most or all integration tests.This approach is valid because:
webEnvironment = MOCKwithMockMvcREQUIRES_NEWDataInitializerruns during context startup and its committed data survives test rollback3. Migrate all integration test classes
Update all 17 integration test classes to:
AbstractIntegrationTest@Testcontainerswhere no longer needed@DirtiesContextannotationsExpected Impact
DataInitializerexecutionsAccepted Tradeoff
Using
@Transactionalon integration tests keeps the Hibernate session open for the duration of each test.As a result, these tests may no longer detect
LazyInitializationExceptionissues that would occur outside a transaction.This is acceptable for this test suite because these integration tests primarily verify API contracts through HTTP request/response behavior. They are not intended to validate transaction boundary behavior.
If needed, a small number of targeted non-transactional tests can be added separately to verify lazy-loading or transaction-boundary behavior.