Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

We might want to override some of our application’s beans in Spring integration testing. Typically, this can be done using Spring Beans specifically defined for testing. However, by providing more than one bean with the same name in a Spring context, we might get a BeanDefinitionOverrideException.

This tutorial will show how to mock or stub integration test beans in a Spring Boot application while avoiding the BeanDefinitionOverrideException.

2. Mock or Stub in Testing

Before digging into the details, we should be confident in how to use a Mock or Stub in testing. This is a powerful technique to make sure our application is not prone to bugs.

We can also apply this approach with Spring. However, direct mocking of integration test beans is only available if we use Spring Boot.

Alternatively, we can stub or mock a bean using a test configuration.

3. Spring Boot Application Example

As an example, let’s create a simple Spring Boot application consisting of a controller, a service, and a configuration class:

@RestController
public class Endpoint {

    private final Service service;

    public Endpoint(Service service) {
        this.service = service;
    }

    @GetMapping("/hello")
    public String helloWorldEndpoint() {
        return service.helloWorld();
    }
}

The /hello endpoint will return a string provided by a service that we want to replace during testing:

public interface Service {
    String helloWorld();
}

public class ServiceImpl implements Service {

    public String helloWorld() {
        return "hello world";
    }
}

Notably, we’ll use an interface. Therefore, when required, we’ll stub the implementation to get a different value.

We also need a configuration to load the Service bean:

@Configuration
public class Config {

    @Bean
    public Service helloWorld() {
        return new ServiceImpl();
    }
}

Finally, let’s add the @SpringBootApplication:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. Overriding Using @MockBean

MockBean has been available since version 1.4.0 of Spring Boot. We don’t need any test configuration. Therefore, it’s sufficient to add the @SpringBootTest annotation to our test class:

@SpringBootTest(classes = { Application.class, Endpoint.class })
@AutoConfigureMockMvc
class MockBeanIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private Service service;

    @Test
    void givenServiceMockBean_whenGetHelloEndpoint_thenMockOk() throws Exception {
        when(service.helloWorld()).thenReturn("hello mock bean");
        this.mockMvc.perform(get("/hello"))
          .andExpect(status().isOk())
          .andExpect(content().string(containsString("hello mock bean")));
    }
}

We are confident that there is no conflict with the main configuration. This is because @MockBean will inject a Service mock into our application.

Finally, we use Mockito to fake the service return:

when(service.helloWorld()).thenReturn("hello mock bean");

5. Overriding Without @MockBean

Let’s explore more options for overriding beans without @MockBean. We’ll look at four different approaches: Spring profiles, conditional properties, the @Primary annotation, and bean definition overriding. We can then stub or mock the bean implementation.

5.1. Using @Profile

Defining profiles is a well-known practice with Spring. First, let’s create a configuration using @Profile:

@Configuration
@Profile("prod")
public class ProfileConfig {

    @Bean
    public Service helloWorld() {
        return new ServiceImpl();
    }
}

Then, we can define a test configuration with our service bean:

@TestConfiguration
public class ProfileTestConfig {

    @Bean
    @Profile("stub")
    public Service helloWorld() {
        return new ProfileServiceStub();
    }
}

The ProfileServiceStub service will stub the ServiceImpl already defined:

public class ProfileServiceStub implements Service {

    public String helloWorld() {
        return "hello profile stub";
    }
}

We can create a test class including the main and test configuration:

@SpringBootTest(classes = { Application.class, ProfileConfig.class, Endpoint.class, ProfileTestConfig.class })
@AutoConfigureMockMvc
@ActiveProfiles("stub")
class ProfileIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void givenConfigurationWithProfile_whenTestProfileIsActive_thenStubOk() throws Exception {
        this.mockMvc.perform(get("/hello"))
          .andExpect(status().isOk())
          .andExpect(content().string(containsString("hello profile stub")));
    }
}

We activate the stub profile in the ProfileIntegrationTest. Therefore, the prod profile is not loaded. Thus, the test configuration will load the Service stub.

5.2. Using @ConditionalOnProperty

Similarly to a profile, we can use the @ConditionalOnProperty annotation to switch between different bean configurations.

Therefore, we’ll have a service.stub property in our main configuration:

@Configuration
public class ConditionalConfig {

    @Bean
    @ConditionalOnProperty(name = "service.stub", havingValue = "false")
    public Service helloWorld() {
        return new ServiceImpl();
    }
}

At runtime, we need to set this condition to false, typically in our application.properties file:

service.stub=false

Oppositely, in the test configuration, we want to trigger the Service load. Therefore, we need this condition to be true:

@TestConfiguration
public class ConditionalTestConfig {

    @Bean
    @ConditionalOnProperty(name="service.stub", havingValue="true")
    public Service helloWorld() {
        return new ConditionalStub();
    }
}

Then, let’s also add our Service stub:

public class ConditionalStub implements Service {

    public String helloWorld() {
        return "hello conditional stub";
    }
}

Finally, let’s create our test class. We’ll set the service.stub conditional to true and load the Service stub:

@SpringBootTest(classes = {  Application.class, ConditionalConfig.class, Endpoint.class, ConditionalTestConfig.class }
, properties = "service.stub=true")
@AutoConfigureMockMvc
class ConditionIntegrationTest {

    @AutowiredService
    private MockMvc mockMvc;

    @Test
    void givenConditionalConfig_whenServiceStubIsTrue_thenStubOk() throws Exception {
        this.mockMvc.perform(get("/hello"))
          .andExpect(status().isOk())
          .andExpect(content().string(containsString("hello conditional stub")));
    }
}

5.3. Using @Primary

We can also use the @Primary annotation. Given our main configuration, we can define a primary service in a test configuration to be loaded with higher priority:

@TestConfiguration
public class PrimaryTestConfig {

    @Primary
    @Bean("service.stub")
    public Service helloWorld() {
        return new PrimaryServiceStub();
    }
}

Notably, the bean’s name needs to be different. Otherwise, we’ll still bump into the original exception. We can change the name property of @Bean or the method’s name.

Again, we need a Service stub:

public class PrimaryServiceStub implements Service {

    public String helloWorld() {
        return "hello primary stub";
    }
}

Finally, let’s create our test class by defining all relevant components:

@SpringBootTest(classes = { Application.class, NoProfileConfig.class, Endpoint.class, PrimaryTestConfig.class })
@AutoConfigureMockMvc
class PrimaryIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void givenTestConfiguration_whenPrimaryBeanIsDefined_thenStubOk() throws Exception {
        this.mockMvc.perform(get("/hello"))
          .andExpect(status().isOk())
          .andExpect(content().string(containsString("hello primary stub")));
    }
}

5.4. Using spring.main.allow-bean-definition-overriding Property

What if we can’t apply any of the previous options? Spring provides the spring.main.allow-bean-definition-overriding property so we can directly override the main configuration.

Let’s define a test configuration:

@TestConfiguration
public class OverrideBeanDefinitionTestConfig {

    @Bean
    public Service helloWorld() {
        return new OverrideBeanDefinitionServiceStub();
    }
}

Then, we need our Service stub:

public class OverrideBeanDefinitionServiceStub implements Service {

    public String helloWorld() {
        return "hello no profile stub";
    }
}

Again, let’s create a test class. If we want to override the Service bean, we need to set our property to true:

@SpringBootTest(classes = { Application.class, Config.class, Endpoint.class, OverribeBeanDefinitionTestConfig.class }, 
  properties = "spring.main.allow-bean-definition-overriding=true")
@AutoConfigureMockMvc
class OverrideBeanDefinitionIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void givenNoProfile_whenAllowBeanDefinitionOverriding_thenStubOk() throws Exception {
        this.mockMvc.perform(get("/hello"))
          .andExpect(status().isOk())
          .andExpect(content().string(containsString("hello no profile stub")));
    }
}

5.5. Using a Mock Instead of a Stub

So far, while using test configuration, we have seen examples with stubs. However, we can also mock a bean. This will work for any test configuration we have seen previously. However, to demonstrate, we’ll follow the profile example.

This time, instead of a stub, we return a Service using the Mockito mock method:

@TestConfiguration
public class ProfileTestConfig {

    @Bean
    @Profile("mock")
    public Service helloWorldMock() {
        return mock(Service.class);
    }
}

Likewise, we make a test class activating the mock profile:

@SpringBootTest(classes = { Application.class, ProfileConfig.class, Endpoint.class, ProfileTestConfig.class })
@AutoConfigureMockMvc
@ActiveProfiles("mock")
class ProfileIntegrationMockTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private Service service;

    @Test
    void givenConfigurationWithProfile_whenTestProfileIsActive_thenMockOk() throws Exception {
        when(service.helloWorld()).thenReturn("hello profile mock");
        this.mockMvc.perform(get("/hello"))
          .andExpect(status().isOk())
          .andExpect(content().string(containsString("hello profile mock")));
    }
}

Notably, this works similarly to the @MockBean. However, we use the @Autowired annotation to inject a bean into the test class. Compared to a stub, this approach is more flexible and will allow us to directly use the when/then syntax inside the test cases.

6. Conclusion

In this tutorial, we learned how to override a bean during Spring integration testing.

We saw how to use @MockBean. Furthermore, we created the main configuration using @Profile or @ConditionalOnProperty to switch between different beans during tests. Also, we have seen how to give a higher priority to a test bean using @Primary.

Finally, we saw a straightforward solution using the spring.main.allow-bean-definition-overriding and override a main configuration bean.

As always, the code presented in this article is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.