Reputation: 32054
Our existing Spring Boot integration setup was using @DirtiesContext
to rebuild the entire bean pool in-between different test methods.
This was fairly slow, and so we started working with beans that could be "refreshed" or torn down/rebuild internally without re-creating the instance.
The problem is that only some beans support this. If we control UsersBean
, we can implement a UsersBean.refresh()
method and call it in our @After
method.
But if we have existing beans/classes that don't support refreshing, or we don't control, how can we conditionally indicate that certain beans need to be dirtied/rebuilt after a specific test?
Or more succinctly: Is there a way to mark as dirty a subsection of your bean pool, for rebuilding, at the end of a test method?
Upvotes: 8
Views: 4435
Reputation: 10931
It looks like this is possible, at least within a Spring Boot environment. The ApplicationContext
implementation there is a GenericApplicationContext which has the ability to removeBeanDefinition(), which can then be re-registered via registerBeanDefinition().
This even cascades through to remove beans that hold a reference to the bean that's being removed (the implementation of this can be seen in DefaultSingletonBeanRegistry.destroyBean()).
For example if Bean1
is referenced by Bean2
:
@Component
public class Bean1 {
}
@Component
public class Bean2 {
@Autowired
public Bean1 bean1;
}
Then a test can remove bean1
from the context, and see bean2
replaced as well:
@RunWith(SpringRunner.class)
public class BeanRemovalTest implements ApplicationContextAware {
@Autowired
private Bean1 bean1;
@Autowired
private Bean2 bean2;
private ApplicationContext applicationContext;
@Test
public void test1() throws Exception {
System.out.println("test1():");
System.out.println(" bean1=" + bean1);
System.out.println(" bean2.bean1=" + bean2.bean1);
resetBean("bean1");
}
@Test
public void test2() throws Exception {
System.out.println("test2():");
System.out.println(" bean1=" + bean1);
System.out.println(" bean2.bean1=" + bean2.bean1);
}
private void resetBean(String beanName) {
GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext;
BeanDefinition bd = genericApplicationContext
.getBeanDefinition(beanName);
genericApplicationContext.removeBeanDefinition("bean1");
genericApplicationContext.registerBeanDefinition("bean1", bd);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}
This shows both bean instances being replaced:
test1():
bean1=hello.so.Bean1@61d6015a
bean2.bean1=hello.so.Bean1@61d6015a
test2():
bean1=hello.so.Bean1@2e570ded
bean2.bean1=hello.so.Bean1@2e570ded
(If the resetBean("bean1")
is commented out, it is the same instance both times round).
There are bound to be edges where this doesn't work out - e.g. if another bean is holding onto a reference obtained from ApplicationContext.getBean()
.
Upvotes: 6