Java 在集成测试中覆盖 bean

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35742920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:04:32  来源:igfitidea点击:

Overriding beans in Integration tests

javaspringspring-bootspring-testspring-web

提问by mvlupan

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 RestTemplate as I dont want to connect to external services - I know what responses to expect. I tried providing a different implementation in the integration-test package in the hope that the latter will override the real implementation , but checking the logs it`s the other way around : the real implementation overrides the test one.

How can I make sure the one from the TestConfig is the one used?

对于我的 Spring-Boot 应用程序,我通过 @Configuration 文件提供了一个 RestTemplate,以便我可以添加合理的默认值(例如超时)。对于我的集成测试,我想模拟 RestTemplate,因为我不想连接到外部服务 - 我知道期望什么响应。我尝试在集成测试包中提供不同的实现,希望后者会覆盖实际实现,但检查日志是相反的:真实实现覆盖测试实现。

如何确保 TestConfig 中的那个是使用的那个?

This is my config file :

这是我的配置文件:

@Configuration
public class RestTemplateProvider {

    private static final int DEFAULT_SERVICE_TIMEOUT = 5_000;

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate(buildClientConfigurationFactory());
    }

    private ClientHttpRequestFactory buildClientConfigurationFactory() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(DEFAULT_SERVICE_TIMEOUT);
        factory.setConnectTimeout(DEFAULT_SERVICE_TIMEOUT);
        return factory;
    }
}

Integration test:

集成测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@WebAppConfiguration
@ActiveProfiles("it")
public abstract class IntegrationTest {}

TestConfiguration class:

测试配置类:

@Configuration
@Import({Application.class, MockRestTemplateConfiguration.class})
public class TestConfiguration {}

And finally MockRestTemplateConfiguration

最后 MockRestTemplateConfiguration

@Configuration
public class MockRestTemplateConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        return Mockito.mock(RestTemplate.class)
    }
}

采纳答案by luboskrnac

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

从 Spring Boot 1.4.x 开始,有一个选项可以使用@MockBean注解来伪造 Spring bean。

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.

要将上下文保留在缓存中,请不要使用@DirtiesContext,而是使用@ContextConfiguration(name = "contextWithFakeBean"),它将创建单独的上下文,同时将默认上下文保留在缓存中。Spring 会将两者(或您拥有的上下文数量)都保存在缓存中。

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

我们的构建是这样的,其中大多数测试使用默认的无污染配置,但我们有 4-5 个测试是伪造的 bean。默认上下文被很好地重用

回答by luboskrnac

1. You can use @Primaryannotation:

1. 可以使用@Primary注解:

@Configuration
public class MockRestTemplateConfiguration {

    @Bean
    @Primary
    public RestTemplate restTemplate() {
        return Mockito.mock(RestTemplate.class)
    }
}

BTW, I wrote blog post about faking Spring bean

顺便说一句,我写了一篇关于伪造 Spring bean 的博客文章

2. But I would suggest to take a look at Spring RestTemplate testing support. This would be simple example:

2. 但我建议看一下Spring RestTemplate testing support。这将是一个简单的例子:

  private MockRestServiceServer mockServer;

  @Autowired
  private RestTemplate restTemplate;

  @Autowired
  private UsersClient usersClient;

  @BeforeMethod
  public void init() {
    mockServer = MockRestServiceServer.createServer(restTemplate);
  }

  @Test
  public void testSingleGet() throws Exception {
    // GIVEN
    int testingIdentifier = 0;
    mockServer.expect(requestTo(USERS_URL + "/" + testingIdentifier))
      .andExpect(method(HttpMethod.GET))
      .andRespond(withSuccess(TEST_RECORD0, MediaType.APPLICATION_JSON));


    // WHEN
    User user = usersClient.getUser(testingIdentifier);

    // THEN
    mockServer.verify();
    assertEquals(user.getName(), USER0_NAME);
    assertEquals(user.getEmail(), USER0_EMAIL);
  }

More examples can be found in my Github repo here

更多示例可以在我的 Github 仓库中找到

回答by Torsten

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

在我的情况下,测试与应用程序不在同一个包中。所以我需要明确指定 AppConfiguration.class(或 App.class)。如果你在测试中使用相同的包,我猜你可以写

@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.

看到这如此不同,真是令人难以置信。也许有人可以解释这一点。直到现在我找不到任何好的答案。您可能会认为,@Import(...)如果@SpringBootTests存在则不会被选中,但是在日志中会显示覆盖的 bean。但只是方法错误。

By the way, using @TestConfigurationinstead @Configurationalso makes no difference.

顺便说一句,使用@TestConfiguration替代@Configuration也没有区别。

回答by Torsten

The Problem in your configuration is that you are using @Configurationfor your test configuration. This will replace your main configuration. Instead use @TestConfigurationwhich will append (override) your main configuration.

您的配置中的问题是您@Configuration用于测试配置。这将替换您的主要配置。而是使用@TestConfigurationwhich 将附加(覆盖)您的主要配置。

46.3.2 Detecting Test Configuration

46.3.2 检测测试配置

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.

如果要自定义主要配置,可以使用嵌套的 @TestConfiguration 类。与嵌套的 @Configuration 类不同,它将用于代替应用程序的主要配置,除了应用程序的主要配置之外,还使用嵌套的 @TestConfiguration 类。

Example using SpringBoot:

使用 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 @Profileif you wish not to instantiate a @Configuration.

注意:请注意,所有 Bean 都将被创建,即使是您覆盖的 Bean。使用@Profile如果您不希望实例化@Configuration

回答by yuranos

Check thisanswer 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.

检查答案以及该线程中提供的其他答案。这是关于覆盖 Spring Boot 2.X 中的 bean,默认情况下禁用此选项。如果您决定走这条路,它也有一些关于如何使用 Bean Definition DSL 的想法。

回答by davidxxx

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

@MockBean和 OP 使用的 bean 覆盖是两种互补的方法。

You want to use @MockBeanto 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.

您想用它@MockBean来创建一个模拟并忘记真正的实现:通常,您这样做是为了切片测试或集成测试,这些测试不会加载您正在测试的类所依赖的某些 bean 以及您不想测试的类这些豆子在整合
Spring 默认生成它们null,您将模拟它们的最小行为来完成您的测试。

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

@WebMvcTest经常需要这种策略,因为您不想测试整个层,并且@SpringBootTest如果您在测试配置中仅指定 bean 配置的一个子集,也可能需要这种策略。

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 @MockBeanbut 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 :

另一方面,有时您希望使用尽可能多的真实组件执行集成测试,因此您不想使用@MockBean但希望稍微覆盖一个行为、一个依赖项或为 bean 定义一个新的范围,在在这种情况下,要遵循的方法是 bean 覆盖:

@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());
        }

    }
}