8

I would like to reuse Spring production context configuration, but replace a few beans with another ones. If I would like to override them with a mock, I would use @MockBean, which does exactly what I need (overrides bean), but does not allow me to configure a new bean myselves.

I know there is another way to use @ContextConfiguration but it seems too verbose to me.

Thanks.

sinedsem
  • 5,413
  • 7
  • 29
  • 46
  • @MockBean is for testing, not for application code. – Antoniossss Feb 06 '19 at 21:26
  • Yes, I am asking about integration testing (unit tests where spring context is being loaded) – sinedsem Feb 07 '19 at 08:53
  • There is no simple way to override bean except to define a new one with the same type and mark it as `@Primary`. `spring.main.allow-bean-definition-overriding=true` can't guarantee what bean will be overridden because of the unpredictable configuration order. Alternative approach would be to exclude production beans from the context (see https://stackoverflow.com/a/48134123/355438) – Ilya Serbis Oct 02 '20 at 00:35

1 Answers1

7

You can use @SpyBean - then bean can be stubbed for specific cases (like in the case of @MockBean), but otherwise real bean will be used.

Also, if you actually need to define custom bean definition for tests, then combination of @Primary / @Profile / @ContextConfiguration can be used for this purpose.

For example:

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
@ContextConfiguration(classes = {TestConfig.class, ApplicationConfig.class})
public class ApplicatonTest {
    @Profile("test")
    @Configuration
    static class TestConfig {

        @Bean
        @Primary
        public SomeBean testBeanDefinition() {
            SomeBean testBean = new SomeBean();
            // configure SomeBean for test
            return testBean;
        }
    }
    // tests
}
Oleksii Zghurskyi
  • 3,967
  • 1
  • 16
  • 17
  • 1) Do I have to mark production beans from ApplicationConfig as `!test`? 2) Don't you think there should be a simplier solution? – sinedsem Feb 07 '19 at 09:06
  • 1) No, there is no need to mark prod beans as `!test`. Profiles overriding takes place. 2) Unfortunately, I don't know simpler way. Usually, when `@MockBean` is not enough, `@SpyBean` solves my needs. Sometimes I had to stub `@SpyBean` to return another `@MockBean` or `@SpyBean` (and so on recursively). But if your use case is really to have custom bean configuration for tests, then `@Configuration` with `@Primary` is the last resort, as far as I know. – Oleksii Zghurskyi Feb 07 '19 at 09:25
  • Does it work even if I already have @Primary bean in production config? – sinedsem Feb 07 '19 at 10:01
  • If exactly one `primary` bean exists among the candidates, it will be the autowired value. – Oleksii Zghurskyi Feb 07 '19 at 10:15
  • yea, but what if I already have primary bean? e.g. Almost all my services uses default http client (which I marked `@Primary`), and some of them uses another http-client-with-proxy (with `@Qualifier`). And I what to ovverride that primary http client in unit test. – sinedsem Feb 07 '19 at 10:21
  • I think [@Order](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html) on @Bean in your test config can help. Just set highest precedence for your test bean, e.g. `@Order(Ordered.HIGHEST_PRECEDENCE)`. – Oleksii Zghurskyi Feb 07 '19 at 10:27
  • With this solution, I still get "Failed to load ApplicationContext" because "Cannot register bean definition (...) for bean (...) There is already [Generic bean: class (...) defined in file (...)". So much magic... – DanielM Jul 24 '20 at 08:00
  • @OleksiiZghurskyi Same issue as DanielM, but the `Order` does not help :( – ch271828n Jul 26 '20 at 08:02
  • Bean overriding feature is disabled by default. You need to enable this feature by switching on an application property `spring.main.allow-bean-definition-overriding` in your test application properties. – nejckorasa Oct 21 '22 at 09:01