Marc Ludwig opened SPR-6121 and commented
Overview
We have a large number of unit/integration tests that assert behavior within our system and rely upon injection of dependencies into configurable domain objects.
These tests all work when executed individually; however, when executed within a suite (either through the IDE or Ant) certain tests fail as dependencies have not been injected into the @Configurable objects. We have also seen dependencies that were configured for test X being injected into test Y rather than the dependencies for test Y; but I have no test case for this.
Steps to Reproduce
I have created a set of three tests -- tests 1 and 3 are basically identical. If these are executed in a suite the third test will fail as the dependency is not injected into the @Configurable object even though it is available to the test. This only occurs if test 2 is a Spring test.
See attached zip file.
Further Resources
- Spring Test Context Caching + AspectJ @Transactional + Ehcache pain blog by Java Code Geeks
Affects: 2.5.6, 3.0.5
Reference URL: http://forum.springsource.org/showthread.php?t=77980
Attachments: - springtest.zip (3.15 MB)
Issue Links: - #15989 SpringContexts not properly closed after test-class is finished ("is duplicated by") - #11019 TestContext framework should support one AspectJ instance per ApplicationContext - #17123 AnnotationTransactionAspect retains reference to JpaTransactionManager from closed context
11 votes, 14 watchers
Comment From: spring-projects-issues
Marc Ludwig commented
Further info. Please note that I have converted the project to use Compile Time Weaving (Eclipse AJDT) and the problem still occurrs.
Comment From: spring-projects-issues
Marc Ludwig commented
Hi Can you advise whether I should expect any progress on this issue.
Can any workaround be suggested, or is the usage of the Spring Test components as per the sample incorrect?
It is currently causing major issues for my employer, and currently I can not find any suitable workaround or fix.
Regards Marc Ludwig
Comment From: spring-projects-issues
Sam Brannen commented
Hi Marc,
I've not yet had time to look into this. That's why this issue has been assigned to 3.1 RC1.
However, you could try annotating your test methods with @DirtiesContext to see if that has any positive effect.
Regards,
Sam
Comment From: spring-projects-issues
Marc Ludwig commented
Within the test case, use of @DirtiesContext on the test methods does solve the issue. However within my production environment this would mean annotating most (if not all) Spring injected tests as order of tests impacts the failures.
Comment From: spring-projects-issues
Tom Denley commented
Hi,
Is there any ongoing activity on this? I'd appreciate an update if possible.
Many thanks, Tom
Comment From: spring-projects-issues
Marc Ludwig commented
Hi I see that this is currently assigned to 3.1 M2. Does it look like this issue will be looked at within the timeline of this release?
Thanks Marc
Comment From: spring-projects-issues
Sam Brannen commented
Related forum thread: http://forum.springsource.org/showthread.php?t=48088
Excerpt:
using Spring 2.5 UnitTests all combinations of context locations are stored isolated in a context cache.
@DirtiesContextallows to invalidate the own cached combination of context-locations. However this isolation is destroyed if one injects into aspectJ-woven Aspects.AspectJ works with its own Singleton. This Singleton is injected everytime a context locations combination is newly created and put into Spring's context cache.
Unfortunately this means if I run 3 subsequent unit-tests:
(1) ac-a.xml injecting foo into my aspect (2) ac-b.xml injecting bar into my aspect (3) ac-a.xml injecting foo into my aspect
then (3) uses caching od ac-a and therfore bar is still injected into my Aspect. So my Unit-Tests' isolation is destroyed as the execution of (2) affects the outcome of (3).
Unfortunately at the end of exectuting (2) it is not possible to dirty / flush the context cached for ac-a (works only for the own context cache entry). We tried to use Testlisteners to flush the whole context-cache but there is no access to the cache. Is there any way to flush the whole Cache??
Comment From: spring-projects-issues
Sam Brannen commented
Custom solution posted in the following blog:
http://object-to-string.blogspot.com/2010/04/make-configurable-work-with-spring-256.html
public class MySpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {
public MySpringJUnit4ClassRunner(Class clazz) throws InitializationError {
super(clazz);
getTestContextManager().registerTestExecutionListeners(
new AbstractTestExecutionListener() {
@Override
public void prepareTestInstance(TestContext testContext)
throws Exception {
Aspects.aspectOf(AnnotationBeanConfigurerAspect.class)
.setBeanFactory(((GenericApplicationContext)testContext.getApplicationContext())
.getDefaultListableBeanFactory());
}
});
}
}
Note, however, that the above anonymous AbstractTestExecutionListener class should preferably be a stand-alone TestExecutionListener registered via @TestExecutionListeners. In that manner, there is no need to subclass SpringJUnit4ClassRunner.
Comment From: spring-projects-issues
Matthew Wise commented
On behalf of Marc Ludwig:
I can confirm that the workaround posted above works for our simple test case. We will now try registering a TestExecutionListener instead.
Comment From: spring-projects-issues
Matthew Wise commented
Registering a TestExecutionListener also works (However we already have a custom subclassed SpringJUnit4ClassRunner so will go with option 1).
I wouldn't consider this a fix, it is a workaround, however it will solve our problems.
Comment From: spring-projects-issues
Frederik De Milde commented
Hello, due to some hibernate issues we also added
Aspects.aspectOf(AnnotationBeanConfigurerAspect.class).destroy();
so the TestExecutionListener should become like
new AbstractTestExecutionListener() {
@Override
public void prepareTestInstance(TestContext testContext)
throws Exception {
Aspects.aspectOf(AnnotationBeanConfigurerAspect.class).destroy();
Aspects.aspectOf(AnnotationBeanConfigurerAspect.class)
.setBeanFactory(((GenericApplicationContext)testContext.getApplicationContext()).getDefaultListableBeanFactory());
}
}
Comment From: spring-projects-issues
Michael Hunger commented
Perhaps the easiest way to solve that is to make a clearCache() method on the default TestContext available. (That also shuts down each of the removed contexts. And even better provide a means to call that, e.g. a separate TestExecutionListener or a flag on @DirtiesContext(clearCache=true)
Comment From: spring-projects-issues
Michael Hunger commented
Destroying the aspects only helps when you know all the aspects that are involved. For instance the @Transactional aspect also holds onto the old context, so whenever you for instance use that, an different transaction-manager could be used than the one you have configured in your current context.
The root cause of this issue is that the context is cached and not reloaded when the third test runs. This should be also noted in different places in the documentation.
Perhaps one should rather add those things as flags to @ContextConfiguration and not a separate @DirtiesContext, because the concern belongs there.
Comment From: spring-projects-issues
DK commented
I'm using Spring 3.1.1-RELEASE with spring-aspects and aspectjrt 1.7.1
I tried both of the above workarounds to no avail. I tried both:
@RunWith(MySpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:/MyFirtTest-context.xml")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class MyFirtTest {
...
}
@RunWith(MySpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:/MySecondTest-context.xml")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class MySecondTest {
...
}
If I add @Ignore to one of the tests everything works.
However, if I include both tests then dependencies don't get injected with @Configurable.
Comment From: spring-projects-issues
Neale Upstone commented
I've just hit this one. It's a horrid pain debugging to know enough to come and find this bug report (and I suspect some people never get here).
I agree with Michael Hunger that it's a ContextConfiguration concern. I think we should have an additional default TestExecutionListener, as shown in this snippet from what I've ended up with.
@TestExecutionListeners({ServletTestExecutionListener.class, // Defaults plus new one
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
AbstractIntegrationTest.AspectBeanFactoryResettingListener.class})
@Transactional
public abstract class AbstractIntegrationTest {
public static final class AspectBeanFactoryResettingListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
DefaultListableBeanFactory factory = ((GenericApplicationContext) testContext.getApplicationContext())
.getDefaultListableBeanFactory();
AnnotationBeanConfigurerAspect.aspectOf().setBeanFactory(factory);
AnnotationTransactionAspect.aspectOf().setBeanFactory(factory);
}
}
...
}
Naturally, there'd be a bit of work to deal with the scenarios when spring-aspects is not present, but I think a new default listener would save lots of people lots of pain (and I'd have been having my lunch an hour ago intead of rushing off now ;-) )
Comment From: spring-projects-issues
Neale Upstone commented
BTW. There's a related bug, #9829, which may merit some consideration here.
Comment From: spring-projects-issues
Eric B commented
I've just encountered this problem / bug myself. I'm amazed that after 6 years, it is still open! Is this so difficult to add to the SpringJUnit4ClassRunner class? Or are there a lot of other considerations that have to be considered?
Comment From: sbrannen
Superseded by #11019