4

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);
    }
}
code4kix
  • 3,937
  • 5
  • 29
  • 44

2 Answers2

6

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

alfcope
  • 2,327
  • 2
  • 13
  • 21
  • I added more detail. Adding a name won't solve what I am trying to achieve. The beans are autowired. – code4kix Jun 23 '17 at 13:31
  • I can not see any problem with the beans being autowired. That is fine. Do you get the same "error" using a name? – alfcope Jun 23 '17 at 14:19
  • Sorry I had some other config issues with profile that caused my previous test to not work. I retested with the `name` attribute, and it works just fine. Also, I don't need `@InjectMocks`. I will update for the benefit of others. – code4kix Jun 23 '17 at 15:14
0

Alternatively, if you use Mockito, you can do this and completely do away with the extra MockitoTestConfiguration class and named primary mock beans in the test profile. Just simply do this:

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

public class FooTest {

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

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @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);
    }
}
code4kix
  • 3,937
  • 5
  • 29
  • 44