84

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

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

这是我的配置文件:

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

集成测试:

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

测试配置类:

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

最后是 MockRestTemplateConfiguration

@Configuration
public class MockRestTemplateConfiguration {

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

10 回答 10

47

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

评论反应:

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

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

于 2017-01-04T02:44:05.690 回答
43

1.可以使用@Primary注解:

@Configuration
public class MockRestTemplateConfiguration {

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

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

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

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

于 2016-03-02T11:47:58.700 回答
22

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.

于 2019-02-14T10:24:56.337 回答
18

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

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

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

另一方面,有时您希望使用尽可能多的真实组件执行集成测试,因此您不想使用@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());
        }

    }
}
于 2019-08-05T13:10:37.427 回答
17

深入了解它,请参阅我的第二个答案

我解决了这个问题

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

代替

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

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

@SpringBootTest(classes = AppTestConfiguration.class)

而不是(不工作)

@Import(AppTestConfiguration.class );

看到这是非常不同的,这是非常明智的。也许有人可以解释这一点。直到现在我都找不到任何好的答案。您可能会认为,如果存在,@Import(...)则不会被拾取,但在日志中会显示覆盖的 bean。@SpringBootTests但只是错误的方式。

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

于 2018-07-31T17:51:30.400 回答
13

使用 @Primary 注释,Bean 覆盖适用于 Spring Boot 1.5.X,但在 Spring Boot 2.1.X 中失败,它会抛出错误:

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

请在下面添加properties=,它将明确指示 Spring 允许覆盖,这是自我解释的。

@SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])

更新:您可以在 application-test.yml 中添加相同的属性(文件名取决于您使用的测试配置文件名称)

于 2020-10-08T08:15:49.167 回答
3

我在测试中声明了一个内部配置类,因为我只想覆盖一个方法

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FileNotificationWebhookTest{

    public static class FileNotificationWebhookTestConfiguration {
        @Bean
        @Primary
        public FileJobRequestConverter fileJobRequestConverter() {
            return new FileJobRequestConverter() {
                @Override
                protected File resolveWindowsPath(String path) {
                    return new File(path);
                }
            };
        }
    }
}

然而,

在@SpringBootTest 中声明配置不起作用

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = {FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class})

或使用 @Configuration 注释测试配置不起作用

@Configuration
public static class FileNotificationWebhookTestConfiguration {

}

并导致

引起:org.springframework.context.ApplicationContextException:无法启动Web服务器;嵌套异常是 org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean。

对我有用的东西(与这里的其他一些帖子相反)是使用@Import

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class)
class FileNotificationWebhookTest {

}

使用 Spring:5.3.3 和 Spring-Boot-Starter:2.4.2

于 2021-03-19T08:10:18.293 回答
0

@MockBean创建 Mockito 模拟而不是生产构建。

如果您不想使用 Mockito,但以其他方式提供替换(即通过功能切换禁用 bean 的某些功能),我建议使用@TestConfiguration(自 Spring Boot 1.4.0 起)和@Primary注释的组合。

@TestConfiguration将加载您的默认上下文并应用您的@TestConfiguration作品。添加@Primary将强制将您模拟的 RestTemplate 注入它的依赖项。

请参见下面的简化示例:

@SpringBootTest
public class ServiceTest {

    @TestConfiguration
    static class AdditionalCfg {
        @Primary
        @Bean
        RestTemplate rt() {
            return new RestTemplate() {
                @Override
                public String exec() {
                    return "Test rest template";
                }
            };
        }
    }

    @Autowired
    MyService myService;

    @Test
    void contextLoads() {
       assertThat(myService.invoke()).isEqualTo("Test rest template");
    }
}

于 2021-07-31T10:21:02.480 回答
0

我找到的最简单的解决方案是在 application.properties 中设置这个属性:

spring.main.allow-bean-definition-overriding=true

这将启用 bean 的覆盖。

接下来,在测试中创建一个配置类,并使用以下命令注释您的 bean:

@Bean
@Primary

这样,这个 bean 将在运行测试时覆盖您通常的 bean。

于 2022-03-05T23:33:36.773 回答
0

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

于 2019-04-04T06:15:50.640 回答