Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test instances should not be proxied in the TestContext framework [SPR-9478] #14113

Closed
spring-issuemaster opened this issue Jun 6, 2012 · 3 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Jun 6, 2012

Peter Ertl opened SPR-9478 and commented

Background

In the TestContext framework, the DependencyInjectionTestExecutionListener (DITEL) is primarily responsible for performing dependency injection into the current test instance, but DITEL also initializes the test instance using AutowireCapableBeanFactory.initializeBean().

Javadoc for initializeBean():

Initialize the given raw bean, applying factory callbacks such as setBeanName and setBeanFactory, also applying all bean post processors (including ones which might wrap the given raw bean).

Note that no bean definition of the given name has to exist in the bean factory. The passed-in bean name will simply be used for callbacks but not checked against the registered bean definitions.

The fact that all registered bean post processors are applied to the test instance as if it were a bean in the ApplicationContext leads to unexpected side effects. For example, a CGLIB enhanced version of the test class may be created in order to proxy a transactional test class that does not implement any interfaces. The generated proxy is never actually used by the TestContext framework, and as such its creation is unnecessary and in fact unintentional.

Case Study Involving Transactional Tests and CGLIB

With a test class like the following:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( ... )
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
@Transactional
public class TransactionalSpringTest {
  // test methods
}

... Spring proxies the test class since it discovers the @Transactional annotation. This is inappropriate since @Transactional is already handled by TransactionalTestExecutionListener via reflection. For every test method execution DependencyInjectionTestExecutionListener indirectly uses CGLIB to enhance the test class. This results in a potentially large number of instrumented CGLIB classes -- the total = number of transactional tests methods per test * number of tests -- each holding a reference to the data inside the test class which are not released but bound to the current class loader.

In our current project we have several classes (around 50-100) marked transactional which in our case results in a huge memory leak (1GB and more). We worked around this issue by letting the test case implement org.springframework.aop.Advisor, which is very dirty but blocks Spring from instrumenting the test class and works fine.

Side Note regarding Dynamic Proxies

Note, however, that if the test class implements any interface then a JDK dynamic proxy will be used instead of CGLIB enhancement. This may occur, for example, if the test class implements ApplicationContextAware like AbstractTransactionalJUnit4SpringContextTests and AbstractTransactionalTestNGSpringContextTests.

Deliverables

  1. Investigate an alternative for bean initialization of test instances that results in neither CGLIB enhancement of the test instances nor creation of dynamic proxies for test instances.

Affects: 3.0 GA

Issue Links:

  • #9309 Load dedicated child ApplicationContext for test instance in the TestContext framework
  • #10719 Provide mechanism for disabling automatic annotation-driven autowiring in tests
  • #21674 Java 10: "Illegal method name" when test functions in Kotlin contain spaces in name
  • #16043 Ignore container callback and marker interfaces for auto-proxy decisions

3 votes, 4 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Feb 6, 2014

Sam Brannen commented

This issue is related to #9309 and #10719 since all of these issues are dependent on how the TestContext framework achieves dependency injection for and initialization of test instances.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 4, 2017

Marten Deinum commented

We recently ran into this issue with a fairly large integration test suite. The application that was being tested was using @SpringBootTest due to it being a Spring Boot application. However the use of @MockBean leads to creation of new ApplicationContext s, which will get cached. To reduce the memory footprint we reduced the number of cached contexts, however memory was still leaking due to the proxies created held on to parts of the context. Leading to leaking 30MB per test proxy (times 1000 testcases and it starts to add up quite quickly).

I'm quite aware of the trade-off here between ease of use and implementing something custom for tests. But it would be nice that there would be some workaround in the testcontext framework that those proxies would get discarded (and gc'ed ) as quickly as possible.

We now implemented the workaround (implementing Advisor) which works, but still it is a workaround (and it probably come back and bite us again).

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 24, 2018

Sam Brannen commented

I have reassigned this issue to Juergen Hoeller, since he graciously and unwittingly resolved this issue while addressing #21674.

I am also marking this issue as resolved.

So let's all give a round of applause to Juergen! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.