Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Configuration - Inject Mock Beans

I am using Spring, Junit and Mockito. I need to override beans defined in the main spring configuration using another mockito test configuration (injecting mock beans only as needed). Nested beans have been @Autowired in the application.

Update:
Based on alfcope's answer below, it is important to add the name attribute so that spring can allow the primary bean (mock) to override the original one. Otherwise you get this exception: org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'. For example: when(mock.getArticles()).thenReturn(articles);

The info message in the spring log shows:
Skipping bean definition for [BeanMethod:name=bar,declaringClass=test.package.MockitoTestConfiguration]: a definition for bean 'bar' already exists. This top-level bean definition is considered as an override.

Example:
I have a simplified example below that works. Here, Bar is the nested inside Foo, and I need to mock Bar for testing:

@Component
public class Foo
{
    @Autowired
    private Bar bar;

    public String getResponseFromBar(String request)
    {
        String response = bar.someMethod(String request);
        //do something else with this reponse
        return response;
    }

} 

@Component
public class Bar {
    public String someMethod(String request) {
        String response = null;
        //do something
        return response;
    }
}

Now for testing, let's say I want to inject a mockbar instead of the real bar. How can I achieve this in my test class below?

@Profile("test")
@Configuration
public class MockitoTestConfiguration {

    //adding the name attribute is important.
    @Bean(name="mockBar")
    @Primary 
    public Bar bar() {
        logger.debug("injecting mock bar");
        return Mockito.mock(Bar.class);
    }
}

Actual test case:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:test-context.xml")

public class FooTest {

    @Autowired
    Foo foo;
    @Autowired
    Bar mockBar; //need this to set up the mock response in the test case.

    @Test
    public void testAMethodInFoo_WithBarInjectedByMockito() {

        //set up the mockBar response
        Mockito.when(mockBar.someMethod("1")).thenReturn("1-response");

        String response = foo.getResponseFromBar();
        assertEquals("1-response", response);
    }
}
like image 396
code4kix Avatar asked Sep 05 '25 05:09

code4kix


1 Answers

Based on the ConfigurationClassBeanDefinitionReader code I guess you are using xml configuration to define your main bean. If so, just add a name when creating your mockito bean.

@Bean(name="mockbar") 
@Primary 
public Bar bar() {
    logger.debug("injecting mock bar");
    return Mockito.mock(Bar.class);
}

This way Spring will allow you to have both beans, and as you are using @Primary it will be the one used by your tests.

Spring overriding primary bean with non-primary bean

ConfigurationClassBeanDefinitionReader Code

like image 150
alfcope Avatar answered Sep 07 '25 21:09

alfcope