41. Testing

Spring Boot 提供了许多 Util 和 Comments,可以在测试应用程序时提供帮助。测试支持由两个模块提供; spring-boot-test包含核心项目,而spring-boot-test-autoconfigure支持自动配置测试。

大多数开发人员只会使用spring-boot-starter-test'Starter',它会导入两个 Spring Boot 测试模块以及 JUnit,AssertJ,Hamcrest 和许多其他有用的库。

41.1 测试范围依赖性

如果您使用spring-boot-starter-test'Starter'(位于test scope中),则会找到以下提供的库:

  • JUnit —用于 Java 应用程序的单元测试的事实上的标准。

  • Spring Test和 Spring Boot 测试—对 Spring Boot 应用程序的 Util 和集成测试支持。

  • AssertJ —流畅的 assert 库。

  • Hamcrest —匹配器对象库(也称为约束或谓词)。

  • Mockito — Java 模拟框架。

  • JSONassert — JSON 的 assert 库。

  • JsonPath — JSON 的 XPath。

Note

默认情况下,Spring Boot 使用 Mockito1.x。但是,如果您愿意,也可以使用 2.x。

这些是我们通常在编写测试时发现有用的通用库。如果不符合您的需求,您可以自由添加其他测试依存关系。

41.2 测试 Spring 应用程序

依赖注入的主要优点之一是,它应该使您的代码更易于进行单元测试。您只需使用new运算符即可实例化对象,甚至无需使用 Spring。您也可以使用* mock objects *代替 true 的依赖。

通常,您需要超越“单元测试”并开始“集成测试”(实际上需要在过程中使用 Spring ApplicationContext)。能够进行集成测试而无需部署应用程序或连接到其他基础结构,这很有用。

Spring 框架包括一个专用的测试模块,用于进行此类集成测试。您可以直接向org.springframework:spring-test声明依赖项,也可以使用spring-boot-starter-test'Starter'将其引入。

如果尚未使用spring-test模块,则应先阅读 Spring Framework 参考文档的relevant section

41.3 测试 Spring Boot 应用程序

Spring Boot 应用程序只是 Spring ApplicationContext,因此除了使用普通的 Spring 上下文进行测试之外,无需执行任何其他非常特殊的操作即可对其进行测试。不过要注意的一件事是,如果使用SpringApplication创建它,则默认情况下仅在上下文中安装 Spring Boot 的外部属性,日志记录和其他功能。

Spring Boot 提供了@SpringBootTest注解,当您需要 Spring Boot 功能时,可以将其用作标准spring-test @ContextConfiguration注解的替代方法。Comments 通过SpringApplication创建在测试中使用的ApplicationContext起作用。

您可以使用@SpringBootTestwebEnvironment属性来进一步完善测试的运行方式:

  • MOCK —加载WebApplicationContext并提供模拟 servlet 环境。使用此 Comments 时,不会启动嵌入式 Servlet 容器。如果 servlet API 不在您的 Classpath 中,则此模式将透明地回退到创建常规的非 Web ApplicationContext。可以与@AutoConfigureMockMvc结合使用,以对您的应用程序进行基于MockMvc的测试。

  • RANDOM_PORT —加载EmbeddedWebApplicationContext并提供真实的 servlet 环境。启动嵌入式 Servlet 容器并在随机端口上侦听。

  • DEFINED_PORT —加载EmbeddedWebApplicationContext并提供真实的 servlet 环境。启动嵌入式 Servlet 容器并在已定义的端口上侦听(即从application.properties或默认端口8080)。

  • NONE —使用SpringApplication加载ApplicationContext,但是不提供* any * servlet 环境(模拟或其他方式)。

Note

如果您的测试是@Transactional,则默认情况下它将在每个测试方法的末尾回滚事务。但是,由于将这种安排与RANDOM_PORTDEFINED_PORT一起使用隐式提供了一个真实的 servlet 环境,因此 HTTPClient 端和服务器将在单独的线程中运行,从而在单独的事务中运行。在这种情况下,服务器上启动的任何事务都不会回滚。

Note

除了@SpringBootTest之外,还提供了许多其他注解,用于测试应用程序的更特定的部分。有关详情,请参见下文。

Tip

别忘了还要在测试中添加@RunWith(SpringRunner.class),否则 Comments 将被忽略。

41.3.1 检测测试配置

如果您熟悉 Spring Test Framework,则可能会习惯使用@ContextConfiguration(classes=…)来指定要加载哪个 Spring @Configuration。另外,您可能经常在测试中使用嵌套的@Configuration类。

在测试 Spring Boot 应用程序时,通常不需要这样做。只要您没有明确定义,Spring Boot 的@*Test注解就会自动搜索您的主要配置。

搜索算法从包含测试的程序包开始工作,直到找到带有@SpringBootApplication@SpringBootConfigurationComments 的类。只要您以合理的方式拥有结构化代码,通常就可以找到您的主要配置。

Note

如果您使用测试 Comments 以测试应用程序的更具体部分进行此类设置,则应避免添加针对主应用程序类上特定区域的配置。

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

Note

Spring 的测试框架将在测试之间缓存应用程序上下文。因此,只要您的测试共享相同的配置(无论如何发现),加载上下文的潜在耗时过程将只发生一次。

41.3.2 排除测试配置

如果您的应用程序使用组件扫描,例如,如果使用@SpringBootApplication@ComponentScan,则可能会偶然发现到处都是为特定测试而创建的顶级配置类。

正如我们看过上面@TestConfiguration可以在测试的内部类上使用以自定义主要配置。当放在顶级类上时,@TestConfiguration指示src/test/java中的类不应通过扫描来拾取。然后,您可以在需要的位置显式导入该类:

@RunWith(SpringRunner.class)
@SpringBootTest
@Import(MyTestsConfiguration.class)
public class MyTests {

    @Test
    public void exampleTest() {
        ...
    }

}

Note

如果您直接使用@ComponentScan(即不通过@SpringBootApplication),则需要向其注册TypeExcludeFilter。有关详情,请参见the Javadoc

41.3.3 使用随机端口

如果您需要启动运行正常的服务器进行测试,建议您使用随机端口。如果您使用@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT),则每次运行测试时都会随机选择一个可用端口。

@LocalServerPort注解可用于注入实际使用的端口进入您的测试。为了方便起见,需要对启动的服务器进行 REST 调用的测试可以另外@AutowireTestRestTemplate来解析到正在运行的服务器的相对链接。

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RandomPortExampleTests {

	@Autowired
	private TestRestTemplate restTemplate;

	@Test
	public void exampleTest() {
		String body = this.restTemplate.getForObject("/", String.class);
		assertThat(body).isEqualTo("Hello World");
	}

}

41.3.4 模拟和 Spybean

在运行测试时,有时有必要在应用程序上下文中模拟某些组件。例如,您可能在开发过程中无法使用某些远程服务的外观。当您要模拟在实际环境中可能难以触发的故障时,模拟功能也很有用。

Spring Boot 包含@MockBean注解,可用于为ApplicationContext中的 bean 定义 Mockito 模拟。您可以使用注解添加新的 bean,或替换单个现有的 bean 定义。注解可以直接用于测试类,测试中的字段或@Configuration类和字段。在字段上使用时,还将注入创建的模拟的实例。每种测试方法后,模拟 bean 都会自动重置。

Note

只要您的测试使用 Spring Boot 的测试 Comments 之一(即@SpringBootTest),就会自动启用此功能。要以其他方式使用此功能,需要显式添加侦听器:

@TestExecutionListeners(MockitoTestExecutionListener.class)

这是一个典型示例,其中我们用模拟实现替换了现有的RemoteService bean:

import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.context.*;
import org.springframework.boot.test.mock.mockito.*;
import org.springframework.test.context.junit4.*;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {

    @MockBean
    private RemoteService remoteService;

    @Autowired
    private Reverser reverser;

    @Test
    public void exampleTest() {
        // RemoteService has been injected into the reverser bean
        given(this.remoteService.someCall()).willReturn("mock");
        String reverse = reverser.reverseSomeCall();
        assertThat(reverse).isEqualTo("kcom");
    }

}

另外,您还可以使用@SpyBean使用 Mockito spy包装任何现有的 bean。有关完整的详细信息,请参见 Javadoc。

41.3.5 自动配置的测试

Spring Boot 的自动配置系统适用于应用程序,但有时对于测试而言可能有点过多。通常,仅加载测试应用程序“切片”所需的配置部分通常会很有帮助。例如,您可能想测试 Spring MVC 控制器是否正确 Map 了 URL,并且您不想在这些测试中涉及数据库调用。或者您可能想要*测试 JPA 实体,并且在运行这些测试时您对 Web 层不感兴趣。

spring-boot-test-autoconfigure模块包含许多 Comments,可用于自动配置此类“切片”。它们中的每一个都以类似的方式工作,提供了一个@…Test注解,该注解加载了ApplicationContext和一个或多个@AutoConfigure…注解,这些注解可用于自定义自动配置设置。

Note

每个片都加载非常有限的一组自动配置类。如果您需要排除其中之一,则大多数@…TestComments 都提供excludeAutoConfiguration属性。或者,您可以使用@ImportAutoConfiguration#exclude

Tip

也可以将@AutoConfigure…Comments 与标准@SpringBootTestComments 一起使用。如果您不希望对应用程序进行“切片”,但需要一些自动配置的测试 bean,则可以使用此组合。

41.3.6 自动配置的 JSON 测试

要测试对象 JSON 序列化和反序列化是否按预期工作,可以使用@JsonTest注解。 @JsonTest将自动配置 Jackson ObjectMapper,任何@JsonComponent bean 和任何 Jackson Modules。如果您正巧用Gson代替 Jackson 或与 Jackson 一样,它也会配置Gson。如果需要配置自动配置的元素,则可以使用@AutoConfigureJsonTestersComments。

Spring Boot 包含基于 AssertJ 的助手,这些助手与 JSONassert 和 JsonPath 库一起使用以检查 JSON 是否符合预期。 JacksonTesterGsonTesterBasicJsonTester类可以分别用于 Jackson,Gson 和 Strings。使用@JsonTest时,测试类上的任何帮助程序字段都可以为@Autowired

import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.autoconfigure.json.*;
import org.springframework.boot.test.context.*;
import org.springframework.boot.test.json.*;
import org.springframework.test.context.junit4.*;

import static org.assertj.core.api.Assertions.*;

@RunWith(SpringRunner.class)
@JsonTest
public class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

    @Test
    public void testSerialize() throws Exception {
        VehicleDetails details = new VehicleDetails("Honda", "Civic");
        // Assert against a `.json` file in the same package as the test
        assertThat(this.json.write(details)).isEqualToJson("expected.json");
        // Or use JSON path based assertions
        assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
        assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make")
                .isEqualTo("Honda");
    }

    @Test
    public void testDeserialize() throws Exception {
        String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
        assertThat(this.json.parse(content))
                .isEqualTo(new VehicleDetails("Ford", "Focus"));
        assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
    }

}

Note

JSON 帮助程序类也可以直接在标准单元测试中使用。如果您不使用@JsonTest,只需在您的@Before方法中调用帮助程序的initFields方法。

@JsonTest启用的自动配置的列表可以为见附录

41.3.7 自动配置的 Spring MVC 测试

要测试 Spring MVC 控制器是否按预期工作,可以使用@WebMvcTestComments。 @WebMvcTest将自动配置 Spring MVC 基础结构,并将扫描的 bean 限制为@Controller@ControllerAdvice@JsonComponentFilterWebMvcConfigurerHandlerMethodArgumentResolver。使用此 Comments 时,不会扫描常规@Component bean。

@WebMvcTest通常会被限制为单个控制器,并与@MockBean结合使用以为所需的协作者提供模拟实现。

@WebMvcTest也会自动配置MockMvc。 Mock MVC 提供了一种强大的方法来快速测试 MVC 控制器,而无需启动完整的 HTTP 服务器。

Tip

您还可以通过用@AutoConfigureMockMvcComments 非@WebMvcTest(例如SpringBootTest)来自动配置MockMvc

import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.autoconfigure.web.servlet.*;
import org.springframework.boot.test.mock.mockito.*;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@WebMvcTest(UserVehicleController.class)
public class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    public void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
                .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
                .andExpect(status().isOk()).andExpect(content().string("Honda Civic"));
    }

}

Tip

如果您需要配置自动配置的元素(例如,当应该应用 servlet 过滤器时),则可以使用@AutoConfigureMockMvc注解中的属性。

如果使用 HtmlUnit 或 Selenium,则自动配置还将提供WebClient bean 和/或WebDriver bean。这是使用 HtmlUnit 的示例:

import com.gargoylesoftware.htmlunit.*;
import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.autoconfigure.web.servlet.*;
import org.springframework.boot.test.mock.mockito.*;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

@RunWith(SpringRunner.class)
@WebMvcTest(UserVehicleController.class)
public class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    public void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
                .willReturn(new VehicleDetails("Honda", "Civic"));
        HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
        assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
    }

}

Note

默认情况下,Spring Boot 会将WebDriver bean 放入特殊的“作用域”中,以确保驱动程序在每次测试后均退出,并确保注入了新实例。如果您不希望出现这种情况,可以将@Scope("singleton")添加到WebDriver @Bean定义中。

@WebMvcTest启用的自动配置的列表可以为见附录

41.3.8 自动配置的数据 JPA 测试

如果要测试 JPA 应用程序,可以使用@DataJpaTest。默认情况下,它将配置一个内存嵌入式数据库,扫描@Entity类并配置 Spring Data JPA 存储库。常规@Component bean 将不会加载到ApplicationContext中。

默认情况下,数据 JPA 测试是事务性的,并且在每个测试结束时都会回滚,有关更多详细信息,请参见 Spring 参考文档中的relevant section。如果这不是您想要的,则可以按以下方式禁用测试或整个类的事务 Management:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class ExampleNonTransactionalTests {

}

数据 JPA 测试也可以注入TestEntityManager bean,以替代专门为测试设计的标准 JPA EntityManager。如果要在@DataJpaTests之外使用TestEntityManager,也可以使用@AutoConfigureTestEntityManagerComments。如果需要,也可以提供JdbcTemplate

import org.junit.*;
import org.junit.runner.*;
import org.springframework.boot.test.autoconfigure.orm.jpa.*;

import static org.assertj.core.api.Assertions.*;

@RunWith(SpringRunner.class)
@DataJpaTest
public class ExampleRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    public void testExample() throws Exception {
        this.entityManager.persist(new User("sboot", "1234"));
        User user = this.repository.findByUsername("sboot");
        assertThat(user.getUsername()).isEqualTo("sboot");
        assertThat(user.getVin()).isEqualTo("1234");
    }

}

内存嵌入式数据库通常运行良好,不需要安装任何开发人员,因此通常可以很好地进行测试。但是,如果您希望对真实数据库运行测试,则可以使用@AutoConfigureTestDatabase注解:

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace=Replace.NONE)
public class ExampleRepositoryTests {

    // ...

}

@DataJpaTest启用的自动配置的列表可以为见附录

41.3.9 自动配置的 JDBC 测试

@JdbcTest@DataJpaTest类似,但适用于与 jdbc 相关的测试。默认情况下,它还将配置一个内存嵌入式数据库和一个JdbcTemplate。常规@Component bean 将不会加载到ApplicationContext中。

缺省情况下,JDBC 测试是事务性的,并且在每个测试结束时都会回滚,有关更多详细信息,请参见 Spring 参考文档中的relevant section。如果这不是您想要的,则可以按以下方式禁用测试或整个类的事务 Management:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class ExampleNonTransactionalTests {

}

如果您希望测试针对真实数据库运行,则可以使用@AutoConfigureTestDatabase注解,就像使用DataJpaTest一样。

@JdbcTest启用的自动配置的列表可以为见附录

41.3.10 自动配置的 Data MongoDB 测试

如果要测试 MongoDB 应用程序,可以使用@DataMongoTest。默认情况下,它将配置内存嵌入式 MongoDB(如果可用),配置MongoTemplate,扫描@Document类并配置 Spring Data MongoDB 存储库。常规@Component bean 将不会加载到ApplicationContext中:

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataMongoTest
public class ExampleDataMongoTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    //
}

内存嵌入式 MongoDB 通常运行良好,不需要安装任何开发人员,因此通常可以很好地用于测试。但是,如果您希望对真实的 MongoDB 服务器运行测试,则应排除嵌入式 MongoDB 自动配置:

import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class ExampleDataMongoNonEmbeddedTests {

}

@DataMongoTest启用的自动配置的列表可以为见附录

41.3.11 自动配置的 RESTClient 端

如果要测试 RESTClient 端,可以使用@RestClientTest注解。默认情况下,它将自动配置 Jackson 和 GSON 支持,配置RestTemplateBuilder并添加对MockRestServiceServer的支持。要测试的特定 bean 应该使用@RestClientTestvaluecomponents属性指定:

@RunWith(SpringRunner.class)
@RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {

    @Autowired
    private RemoteVehicleDetailsService service;

    @Autowired
    private MockRestServiceServer server;

    @Test
    public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
            throws Exception {
        this.server.expect(requestTo("/greet/details"))
                .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
        String greeting = this.service.callRestService();
        assertThat(greeting).isEqualTo("hello");
    }

}

@RestClientTest启用的自动配置的列表可以为见附录

41.3.12 自动配置的 Spring REST Docs 测试

如果要在测试中使用 Spring REST Docs,则可以使用@AutoConfigureRestDocs注解。它将自动配置MockMvc以使用 Spring REST Docs,并不需要 Spring REST Docs 的 JUnit 规则。

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs("target/generated-snippets")
public class UserDocumentationTests {

    @Autowired
    private MockMvc mvc;

    @Test
    public void listUsers() throws Exception {
        this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
                .andExpect(status().isOk())
                .andDo(document("list-users"));
    }

}

@AutoConfigureRestDocs除了配置输出目录外,还可以配置将出现在任何记录的 URI 中的主机,方案和端口。如果您需要对 Spring REST Docs 的配置进行更多控制,则可以使用RestDocsMockMvcConfigurationCustomizer bean:

@TestConfiguration
static class CustomizationConfiguration
        implements RestDocsMockMvcConfigurationCustomizer {

    @Override
    public void customize(MockMvcRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}

如果要使用 Spring REST Docs 对参数化输出目录的支持,可以创建RestDocumentationResultHandler bean。自动配置将使用此结果处理程序调用alwaysDo,从而使每个MockMvc调用自动生成默认代码段:

@TestConfiguration
static class ResultHandlerConfiguration {

    @Bean
    public RestDocumentationResultHandler restDocumentation() {
        return MockMvcRestDocumentation.document("{method-name}");
    }

}

41.3.13 用户配置和切片

如果您以合理的方式拥有结构化代码,则您的@SpringBootApplication类为默认使用作为测试的配置。

因此,重要的是不要用特定于其功能特定区域的配置来乱扔应用程序的主类。

假设您使用的是 Spring Batch,并且依赖于自动配置。您可以如下定义您的@SpringBootApplication

@SpringBootApplication
@EnableBatchProcessing
public class SampleApplication { ... }

因为此类是测试的源配置,所以任何切片测试实际上都将尝试启动 Spring Batch,这绝对不是您想要执行的操作。建议的方法是将特定于区域的配置移到与应用程序相同级别的单独的@Configuration类。

@Configuration
@EnableBatchProcessing
public class BatchConfiguration { ... }

Note

根据您应用程序的表面积,您可以为您的自定义设置一个ApplicationConfiguration类,也可以在每个域区域一个类。后一种方法允许您在必要时通过@Import在一个测试中启用它。

混乱的另一个来源是 Classpath 扫描。假设在以合理的方式组织代码的同时,您需要扫描其他程序包。您的应用程序可能如下所示:

@SpringBootApplication
@ComponentScan({ "com.example.app", "org.acme.another" })
public class SampleApplication { ... }

这有效地覆盖了默认的组件扫描指令,并具有扫描这两个程序包的副作用,而与您选择的切片无关。例如,@DataJpaTest将突然扫描您的应用程序的组件和用户配置。同样,将自定义指令移至单独的类是解决此问题的好方法。

Tip

如果这不是您的选择,则可以在测试层次结构中的某个位置创建@SpringBootConfiguration,以便代替它使用。或者,您可以为测试指定一个源,这将禁用查找默认源的行为。

41.3.14 使用 Spock 测试 Spring Boot 应用程序

如果您希望使用 Spock 测试 Spring Boot 应用程序,则应在应用程序的构建中添加对 Spock 的spock-spring模块的依赖。 spock-spring将 Spring 的测试框架集成到了 Spock 中。究竟如何使用 Spock 测试 Spring Boot 应用程序取决于您所使用的 Spock 版本。

Note

Spring Boot 为 Spock 1.0 提供了依赖 Management。如果您想使用 Spock 1.1,则应在build.gradlepom.xml文件中覆盖 spock.version 属性

使用 Spock 1.1 时,只能使用注解described above,并且可以使用@SpringBootTestCommentsSpecification以满足测试的需要。

使用 Spock 1.0 时,@SpringBootTest不适用于 Web 项目。您需要使用@SpringApplicationConfiguration@WebIntegrationTest(randomPort = true)。无法使用@SpringBootTest意味着您还会丢失自动配置的TestRestTemplate bean。您可以使用以下配置自行创建等效的 bean:

@Configuration
static class TestRestTemplateConfiguration {

    @Bean
    public TestRestTemplate testRestTemplate(
            ObjectProvider<RestTemplateBuilder> builderProvider,
            Environment environment) {
        RestTemplateBuilder builder = builderProvider.getIfAvailable();
        TestRestTemplate template = builder == null ? new TestRestTemplate()
                : new TestRestTemplate(builder.build());
        template.setUriTemplateHandler(new LocalHostUriTemplateHandler(environment));
        return template;
    }

}

41.4 测试 Util

一些测试 Util 类被打包为spring-boot的一部分,通常在测试您的应用程序时很有用。

41.4.1 ConfigFileApplicationContextInitializer

ConfigFileApplicationContextInitializerApplicationContextInitializer,可以应用于测试以加载 Spring Boot application.properties文件。当您不需要@SpringBootTest提供的全部功能时,可以使用此功能。

@ContextConfiguration(classes = Config.class,
    initializers = ConfigFileApplicationContextInitializer.class)

Note

单独使用ConfigFileApplicationContextInitializer不会为@Value("${…}")注入提供支持。它唯一的工作就是确保将application.properties文件加载到 Spring 的Environment中。要获得@Value支持,您需要另外配置PropertySourcesPlaceholderConfigurer或使用@SpringBootTest,其中将自动为您配置一个。

41.4.2 EnvironmentTestUtils

EnvironmentTestUtils可让您快速将属性添加到ConfigurableEnvironmentConfigurableApplicationContext。只需使用key=value字符串调用它即可:

EnvironmentTestUtils.addEnvironment(env, "org=Spring", "name=Boot");

41.4.3 OutputCapture

OutputCapture是一个 JUnit Rule,可用于捕获System.outSystem.err输出。只需将捕获声明为@Rule,然后将toString()用于 assert:

import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class MyTest {

    @Rule
    public OutputCapture capture = new OutputCapture();

    @Test
    public void testName() throws Exception {
        System.out.println("Hello World!");
        assertThat(capture.toString(), containsString("World"));
    }

}

41.4.4 TestRestTemplate

TestRestTemplate是 Spring RestTemplate的便捷替代方法,在集成测试中非常有用。您可以使用普通模板或发送基本 HTTP 身份验证(带有用户名和密码)的模板。在这两种情况下,模板都不会通过在服务器端错误上引发异常来以易于测试的方式运行。建议(但不是强制性的)使用 Apache HTTP Client(版本 4.3.2 或更高版本),如果您在 Classpath 中使用了它,则TestRestTemplate将通过适当配置 Client 端来响应。如果您确实使用 Apache 的 HTTPClient 端,则会启用一些其他易于测试的功能:

  • 重定向不会被执行(因此您可以声明响应位置)

  • Cookies 将被忽略(因此模板是 Stateless 的)

TestRestTemplate可以直接在集成测试中实例化:

public class MyTest {

    private TestRestTemplate template = new TestRestTemplate();

    @Test
    public void testRequest() throws Exception {
        HttpHeaders headers = template.getForEntity("http://myhost.com/example", String.class).getHeaders();
        assertThat(headers.getLocation().toString(), containsString("myotherhost"));
    }

}

另外,如果您将@SpringBootTestComments 与WebEnvironment.RANDOM_PORTWebEnvironment.DEFINED_PORT一起使用,则只需注入一个完整配置的TestRestTemplate并开始使用它。如有必要,可以通过RestTemplateBuilder bean 应用其他定制。任何未指定主机和端口的 URL 将自动连接到嵌入式服务器:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {

    @Autowired
    private TestRestTemplate template;

    @Test
    public void testRequest() throws Exception {
        HttpHeaders headers = template.getForEntity("/example", String.class).getHeaders();
        assertThat(headers.getLocation().toString(), containsString("myotherhost"));
    }

    @TestConfiguration
    static class Config {

        @Bean
        public RestTemplateBuilder restTemplateBuilder() {
            return new RestTemplateBuilder()
                .additionalMessageConverters(...)
                .customizers(...);
        }

    }

}