Description
John Wu opened SPR-6353 and commented
The Symptom
- Each test passes when running individually;
- A few
@Transactional
tests may fail when running in a test suite. The failure may occur on any@Transactional
test class, depending on the nondeterministic runtime tests sequence. In fact, it's quite difficult to reproduce before knowing the root cause.
Steps to Reproduce
- Using Spring 2.5.6 or 3.0.0.RC1, JUnit 4;
- Create a
SimpleDbOpService
(may or may not annotated with@Transactional
), add a method to insert a record into DB based on the input parameters; - Create a SimpleDbOpATest:
@ContextConfiguration({"base.xml", "A.xml"})
- Create a SimpleDbOpBTest:
@ContextConfiguration({"base.xml", "B.xml"})
- Create a SimpleDbOpCTest:
@ContextConfiguration({"base.xml", "A.xml"})
- Each test is annotated with
@Transactional
, calling theSimpleDbOpService
and verifying the DB operation results at the end of test method; <tx:annotation-driven mode="aspectj"/>
inbase.xml
, also add "transactionManager" definition in- Create a test suite class as the following:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
SimpleDbOpATest.class,
SimpleDbOpBTest.class,
SimpleDbOpCTest.class
})
public class Debug {
}
- The tests in
SimpleDbOpCTest
will fail.
Root Cause
In the Test Framework, it creates one instance of ApplicationContext
for each "Set of context locations" and caches (and switches) the ApplicationContext instances by "context locations". Among all those ApplicationContext
instances, the instance of AnnotationTransactionAspect
is shared. That is, during the test suite running, there will be only one instance of AnnotationTransactionAspect
, no matter how many ApplicationContext
instances created.
A reference to BeanFactory
(in spring 3.0.0.RC) or transactionManager
(in spring 2.5.6) will be injected into AnnotationTransactionAspect
(derived from TransactionAspectSupport
) while the ApplicationContext
being loaded.
In the example above, test A and C have the exactly same context locations, and they will share the same application context instance when running in a test suite. So, when running tests A, B, and C in a suite and in that order, the application context instance switches from A to B to A. However, the transactionManager
instance (retrieved from an instance of AnnotationTransactionAspect
) will be A, B, and B (should be A though), because there is only one instance of AnnotationTransactionAspect
per class loader. In turn, that causes the operations in the C.testXxx()
be split in to two transactionManager
instances, one from AnnotationTransactionAspect
and the other from ApplicationContext
. Therefore, the DB result verification fails.
Proposed Solution
To create one Aspect instance per ApplicationContext
, not per class loader. Not sure if that's achievable though.
Further Resources
- Spring Test Context Caching + AspectJ @Transactional + Ehcache pain blog by Java Code Geeks
Related Issues
- Transaction is not started when two spring contexts are created during tests using AspectJ load time weaving [SPR-7238] #11897
- AnnotationTransactionAspect retains reference to JpaTransactionManager from closed context [SPR-12518] #17123
- Dependency injection of @Configurable objects should work across test suites [SPR-6121] #10789
- AnnotationTransactionAspect retains reference to closed BeanFactory [SPR-7964] #12619
- AspectJ, multi-context, @Lock: no transaction is in progress #24794
- @DirtiesContext, @Async and @EnableAsync(mode = AdviceMode.ASPECTJ) #30229
Affects: 2.5.6, 3.0.5
Reference URL: http://forum.springsource.org/showthread.php?t=79949
5 votes, 7 watchers