在Integration测试中覆盖bean

时间:2016-03-02 09:18:26

标签: java spring spring-boot spring-test spring-web

对于我的Spring-Boot应用程序,我通过@Configuration文件提供了RestTemplate,因此我可以添加合理的默认值(ex Timeouts)。对于我的集成测试,我想模拟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 {}

TestConfiguration类:

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

最后是MockRestTemplateConfiguration

@Configuration
public class MockRestTemplateConfiguration {

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

9 个答案:

答案 0 :(得分:20)

1。 您可以使用@Primary注释:

@Configuration
public class MockRestTemplateConfiguration {

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

BTW,我写了blog post about faking 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);
  }

更多示例可以在my Github repo here

中找到

答案 1 :(得分:20)

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

评论反应:

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

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

答案 2 :(得分:9)

深入了解它,看看我的第二个答案

我使用

解决了问题
@SpringBootTest(classes = {AppConfiguration.class, AppTestConfiguration.class})

代替

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

就我而言,测试与应用不在同一个程序包中。因此,我需要明确指定AppConfiguration.class(或App.class)。如果您在测试中使用相同的程序包,那么我猜您可以编写

@SpringBootTest(classes = AppTestConfiguration.class)

代替(不起作用)

@Import(AppTestConfiguration.class );

很高兴看到这是如此不同。也许有人可以解释这一点。到目前为止,我找不到任何好的答案。您可能会认为,如果@Import(...)存在,@SpringBootTests不会被拾取,但是在日志中会显示覆盖的bean。但是只是错误的方法。

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

答案 3 :(得分:5)

配置中的问题是您正在使用@Configuration进行测试配置。这将替换您的主要配置。而是使用 @TestConfiguration ,它将附加(覆盖)您的主要配置。

46.3.2 Detecting Test Configuration

  

如果要自定义主要配置,则可以使用   嵌套的@TestConfiguration类。与嵌套的@Configuration类不同,   它将用来代替您的应用程序的主要版本   配置,另外使用嵌套的@TestConfiguration类   应用程序的主要配置。

使用SpringBoot的示例:

主班

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

主要配置

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

测试配置

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

测试课程

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

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

答案 4 :(得分:3)

使用@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"])

答案 5 :(得分:2)

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

您想使用@MockBean创建一个模拟并忘了实际的实现:通常,您在进行切片测试或集成测试时会这样做,因为这些测试不会加载您要测试的类所依赖的bean,并且您不想在集成中测试这些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());
        }

    }
}

答案 6 :(得分:1)

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

@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: 无法启动网络服务器;嵌套异常是 org.springframework.context.ApplicationContextException:无法 由于缺少启动 ServletWebServerApplicationContext 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

答案 7 :(得分:0)

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

答案 8 :(得分: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");
    }
}