Sunday, April 28, 2013

Spring beans with same name and @Configuration

One of the important features when testing an application is being able to replace some of the real services with test doubles. With a Spring based application, this has typically been done by defining the test doubles with the same name as the original bean which is being mocked out and this works as the bean defined later is the one which is finally instantiated by the Spring container.

Consider a bean, call it memberService bean, having two implementations one defined using XML based bean definition files say in a context1.xml file:

<bean name="memberService" class="samples.config.MemberSvcImpl1"/>

and if a different implementation of memberService is needed for test, the way to do it would have been to define the bean with the same name, this way(say in a context2.xml file):

<import resource="context1.xml"/>
<bean name="memberService" class="samples.config.MemberSvcImpl2"/>

since the MemberSvcImpl2 is defined after MemberSvcImpl1, the MemberSvcImpl2 would be available for injection in tests.


However with @Configuration style of defining beans, things are not so clear cut. What does it mean to define a bean after another bean, consider the following which tries to mimic the definition in context2.xml file above:

@Configuration
@ImportResource("classpath:samples/config/context1.xml")
public class Context2Config {
 @Bean
 public MemberService memberService() {
  return new MemberSvcImpl2();
 }
}

Here the memberService defined in the @Configuration DOES NOT override the memberService bean defined in the xml. Something to be careful about.

The solution is actually simple - if you need to override a previously defined bean(without say the flexibility of autowiring with a different bean name), either use the XML bean configuration for both the bean being overridden and the overriding bean or use the @Configuration. XML bean configuration is the first example in this entry, the one with @Configuration would be something like this:

@Configuration
public class Context1JavaConfig {
 @Bean
 public MemberService memberService() {
  return new MemberSvcImpl1();
 }
}

@Configuration
@Import(Context1JavaConfig.class)
public class Context2JavaConfig {
 @Bean
 public MemberService memberService() {
  return new MemberSvcImpl2();
 }
}

This would work as expected. I am sure there are alternate ways of getting this to work, but the approach that I have outlined here has worked well for me and I have been sticking to this pattern

6 comments:

  1. yes, my search for injecting mocks into spring ctx has finally ended, nicely summarized solution, there's truly a pile on stackoverflow on this topic

    ReplyDelete
  2. yes, my search for injecting mocks into spring ctx has finally ended, nicely summarized/clean solution here, there's truly a pile on stackoverflow on this topic

    ReplyDelete
  3. Thanks... my last comment just disappeared???

    How do you make the load-and-override order deterministic when using @Configuration?

    ReplyDelete
  4. I tried the @Configuration approach and found that Context2 memberService was overriden by the Context1 memberService, the opposite of what is intended.

    ReplyDelete
    Replies
    1. Hi @dantheperson, it is possible, when I wrote this article I was using an older version of Spring, the behavior may have changed with newer versions, I will check and let you know.

      Delete