Spring overriding primary bean with non-primary bean

18,363

Solution 1

@Primary takes effect only at injection point, when there is a conflict because different beans match the condition to be injected, and a decision needs to be made.

@Primary is not used at beans initialisation. As you are using two different methods creating the same bean, and you are not naming any of them Spring considers you are trying to override it, so this behaviour can happen. Given a name is the easiest solution, but bear in mind that your context will still be initialising the bean you do not want use.

Solution 2

I think you might be missing @ContextConfiguration in your test class.

Example of test configuration class (src/test/java/TestConfiguration.class):

@Configuration
@ComponentScan
public class TestConfiguration {
    @Bean
    RabbitSender rabbitSender() {
        return mock(RabbitSender.class);
    }

}

Example of test class:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class SomeServiceTest {

}
Share:
18,363

Related videos on Youtube

FiguringThisOut
Author by

FiguringThisOut

Updated on June 06, 2022

Comments

  • FiguringThisOut
    FiguringThisOut almost 2 years

    I am trying to override a Spring bean during a test declared in a test configuration with the use of @Primary. One declaration is in the src/main/java path, the other, the primary, is in src/test/java path.

    However, Spring is intentionally replacing the primary bean with the the non-primary bean, the one I don't want to use for the test. If I simply comment out the production (src/main/java) configuration bean, it uses the primary test (src/main/test) bean in the test configuration as desired. (Clearly I can't comment out code every time I want to run a test.)

    From the logs:

    o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'sqsConnectionFactory' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; factoryBeanName=testJmsConfiguration; factoryMethodName=sqsConnectionFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/foo/configuration/TestJmsConfiguration.class]]

    with

    [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=jmsConfiguration; factoryMethodName=sqsConnectionFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/foo/configuration/JmsConfiguration.class]]

    Why is spring replacing a primary bean with a non-primary bean and how do I get Spring to use the bean specifically marked as the primary bean?

    Edit: The src/main/java configuration:

    @Configuration
    public class JmsConfiguration {
    
    ... other bean declarations here ...
    
    @Bean
    public SQSConnectionFactory sqsConnectionFactory(Region region) throws JMSException {
        return SQSConnectionFactory.builder()
                .withRegion(region)
                .build();
    }
    }
    

    The test configuration:

    @Configuration
    public class TestJmsConfiguration {
    
    @Bean(name="messageProducerMock")
    public MessageProducer mockMessageProducer() {
        return new MessageProducerMock();
    }
    
    ... other bean declarations here ...
    
    @Bean
    @Primary
    public SQSConnectionFactory sqsConnectionFactory(@Qualifier("messageProducerMock") MessageProducer messageProducerMock) throws JMSException {
        ... returning setup mock here
    }
    }
    

    The class with the tests is annotated with:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ActiveProfiles(profiles = {"test"})
    
    • reos
      reos about 7 years
      Please add the test config and the test class that has the problem.
    • alfcope
      alfcope about 7 years
      Two different method creating a bean with the same name, so that behaviour can happen. Give a name to your primary bean: @Bean(name="nameforbean")
    • FiguringThisOut
      FiguringThisOut about 7 years
      @alfcope That is the answer, thanks! I'll accept it as correct if you submit this as an answer. I don't know why Spring would allow that behavior when they know that they are replacing a bean marked primary with one that is not marked primary.
    • alfcope
      alfcope about 7 years
      I am glad it works @FiguringThisOut. There you have an answer with a little explanation.
  • FiguringThisOut
    FiguringThisOut about 7 years
    The current configuration is loading the primary bean as desired, it is just that the non-primary bean is then replacing it. What does manually adding @ContextConfiguration do differently? I still need the other configuration class that contains the bean replacing the primary bean because it has other bean declarations in it.
  • hya
    hya about 7 years
    If do something like this, you dont need to write @Primary. Bean in TestConfiguration class will override "primary" bean. This annotations tells Spring that you want to use specific configuration for specific test, so it will override others.
  • FiguringThisOut
    FiguringThisOut about 7 years
    Thank you for the solution and explanation.
  • Anders Rabo Thorbeck
    Anders Rabo Thorbeck almost 6 years
    Changing the method name of one of the conflicting methods annotated with @Bean also seems to work as a fix (to differentiate the bean definitions).