Overriding beans in Integration tests

后端 未结 7 1793
眼角桃花
眼角桃花 2020-12-23 09:16

For my Spring-Boot app I provide a RestTemplate though a @Configuration file so I can add sensible defaults(ex Timeouts). For my integration tests I would like to mock the R

相关标签:
7条回答
  • 2020-12-23 09:34

    Since Spring Boot 1.4.x there is an option to use @MockBean annotation to fake Spring beans.

    Reaction on comment:

    To keep context in cache do not use @DirtiesContext, but use @ContextConfiguration(name = "contextWithFakeBean") and it will create separate context, while it will keep default context in cache. Spring will keep both (or how many contexts you have) in cache.

    Our build is this way, where most of the tests are using default non-poluted config, but we have 4-5 tests that are faking beans. Default context is nicely reused

    0 讨论(0)
  • 2020-12-23 09:38

    The Problem in your configuration is that you are using @Configuration for your test configuration. This will replace your main configuration. Instead use @TestConfiguration which will append (override) your main configuration.

    46.3.2 Detecting Test Configuration

    If you want to customize the primary configuration, you can use a nested @TestConfiguration class. Unlike a nested @Configuration class, which would be used instead of your application’s primary configuration, a nested @TestConfiguration class is used in addition to your application’s primary configuration.

    Example using SpringBoot:

    Main class

    @SpringBootApplication() // Will scan for @Components and @Configs in package tree
    public class Main{
    }
    

    Main config

    @Configuration
    public void AppConfig() { 
        // Define any beans
    }
    

    Test config

    @TestConfiguration
    public void AppTestConfig(){
        // override beans for testing
    } 
    

    Test class

    @RunWith(SpringRunner.class)
    @Import(AppTestConfig.class)
    @SpringBootTest
    public void AppTest() {
        // use @MockBean if you like
    }
    

    Note: Be aware, that all Beans will be created, even those that you override. Use @Profile if you wish not to instantiate a @Configuration.

    0 讨论(0)
  • 2020-12-23 09:42

    Getting a little deeper into it, see my second answer.

    I solved the Problem using

    @SpringBootTest(classes = {AppConfiguration.class, AppTestConfiguration.class})
    

    instead of

    @Import({ AppConfiguration.class, AppTestConfiguration.class });
    

    In my case the Test is not in the same package as the App. So I need to specify the AppConfiguration.class (or the App.class) explicit. If you use the same package in the test, than I guess you could just write

    @SpringBootTest(classes = AppTestConfiguration.class)
    

    instead of (not working)

    @Import(AppTestConfiguration.class );
    

    It is pretty wired to see that this is so different. Maybe some one can explain this. I could not find any good answers until now. You might think, @Import(...) is not picked up if @SpringBootTestsis present, but in the log the overriding bean shows up. But just the wrong way around.

    By the way, using @TestConfiguration instead @Configuration also makes no difference.

    0 讨论(0)
  • 2020-12-23 09:44

    @MockBean and bean overriding used by the OP are two complementary approaches.

    You want to use @MockBean to create a mock and forget the real implementation : generally you do that for slice testing or integration testing that doesn't load some beans which class(es) you are testing depend on and that you don't want to test these beans in integration.
    Spring makes them by default null, you will mock the minimal behavior for them to fulfill your test.

    @WebMvcTest requires very often that strategy as you don't want to test the whole layers and @SpringBootTest may also require that if you specify only a subset of your beans configuration in the test configuration.

    On the other hand, sometimes you want to perform an integration test with as many real components as possible, so you don't want to use @MockBean but you want to override slightly a behavior, a dependency or define a new scope for a bean, in this case, the approach to follow is bean overriding :

    @SpringBootTest({"spring.main.allow-bean-definition-overriding=true"})
    @Import(FooTest.OverrideBean.class)
    public class FooTest{    
    
        @Test
        public void getFoo() throws Exception {
            // ...     
        }
    
        @TestConfiguration
        public static class OverrideBean {    
    
            // change the bean scope to SINGLETON
            @Bean
            @Scope(ConfigurableBeanFactory.SINGLETON)
            public Bar bar() {
                 return new Bar();
            }
    
            // use a stub for a bean 
            @Bean
            public FooBar BarFoo() {
                 return new BarFooStub();
            }
    
            // use a stub for the dependency of a bean 
            @Bean
            public FooBar fooBar() {
                 return new FooBar(new StubDependency());
            }
    
        }
    }
    
    0 讨论(0)
  • 2020-12-23 09:44

    Check this answer along with others provided in that thread. It's about overriding bean in Spring Boot 2.X, where this option was disabled by default. It also has some ideas about how to use Bean Definition DSL if you decided to take that path.

    0 讨论(0)
  • 2020-12-23 09:48

    With @Primary annotation, Bean overriding works with Spring Boot 1.5.X but fails with Spring Boot 2.1.X it throw error:

    Invalid bean definition with name 'testBean' defined in sample..ConfigTest$SpringConfig:.. 
    There is already .. defined in class path resource [TestConfig.class]] bound
    

    Please add below properties= which will instruct Spring explicitly to allow overriding, it is self explainatory.

    @SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])
    
    0 讨论(0)
提交回复
热议问题