43. 测试

在测试应用程序时,Spring Boot 提供了许多使用工具和注解。测试支持由两个模块提供:spring-boot-test 包含核心项,spring-boot-test-autoconfigure 支持测试的自动配置。

大多数开发人员使用 spring-boot-starter-test “Starter”,它既导入 Spring Boot 测试模块,又导入了 JUnit, AssertJ, Hamcrest,以及许多其它有用的库。

43.1 测试范围依赖项

spring-boot-starter-test “Starter”(在 test scope 内)包含以下提供的库:

  • JUnit: 单元测试 Java 应用程序的实际标准。
  • Spring Test & Spring Boot Test: Spring Boot 应用程序的单元和集成测试支持。
  • AssertJ: 流式断言的库。
  • Hamcrest: 匹配对象的库(也称为约束或谓词)。
  • Mockito: Java 模拟框架。
  • JSONassert: JSON 断言库。
  • JsonPath: JSON 的 XPath。

在编写测试时,我们通常发现这些公共库是有用的。如果这些库不适合你的需求,你可以添加自己的其它测试依赖项。

43.2 测试 Spring 应用程序

依赖注入的一个主要优点是它应该使你的代码更容易进行单元测试。可以使用新运算符实例化对象,甚至不涉及 Spring。还可以使用模拟对象替代实际依赖项。

通常,你需要越过单元测试并开始集成测试(使用 Spring ApplicationContext)。在不需要部署应用程序或需要连接到其它基础设施的情况下,能够执行集成测试是有用的。

Spring Framework 包括用于这种集成测试的专用测试模块。你可以直接向 org.springframework:spring-test 声明一个依赖项,或者使用 spring-boot-starter-test “Starter” 来对它进行传递。

如果以前没有使用 spring-test 模块,则应该先阅读 Spring Framework 参考文档的r相关部分。

43.3 测试 Spring Boot 应用程序

Spring Boot 应用程序是一个 Spring ApplicationContext,所以测试它也没什么超过测试普通的 Spring 上下文的特别事情要做。

[Note] Note

只有当你使用 SpringApplication 创建时,Spring Boot 的外部属性,日志记录和其它特性才会默认在上下文中安装。

Spring Boot 提供了一个 @SpringBootTest 注解,当你需要 Spring Boot 特性时,它可以作为标准 spring-test @ContextConfiguration 注解的替代。注解通过 SpringApplication 创建你测试中使用的 ApplicationContext 来工作。除了 @SpringBootTest 之外,还提供了许多其它注解,用于应用程序测试更具体的部分。

[Tip] Tip

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

可以使用 @SpringBootTest 的 webEnvironment 属性进一步细化测试的运行方式:

  • MOCK: 加载 WebApplicationContext 并提供模拟 servlet 环境。使用此注解时,未启动嵌入式 servlet 容器。如果 servlet API 不在类路径上,则该模式透明地回落到创建常规的 non-web ApplicationContext。它在测试应用程序时可以与基于 MockMvc 的 @AutoConfigureMockMvc 结合使用
  • RANDOM_PORT: 加载 ServletWebServerApplicationContext 并提供真正的 servlet 环境。嵌入式 servlet 容器启动并在随机端口上监听
  • DEFINED_PORT: 加载 ServletWebServerApplicationContext 并提供真正的 servlet 环境。嵌入式 servlet 容器启动并监听定义的端口(从你的 application.properties 或默认端口 8080)。
  • NONE: 使用 SpringApplication 加载 ApplicationContext,但不提供任何 servlet 环境(模拟或其他)。
[Note] Note

如果你的测试是 @Transactional,则在默认情况下,在每个测试方法的末尾回滚事务。然而,由于使用 RANDOM_PORT 或 DEFINED_PORT 的这种配置隐含地提供了一个真正的 servlet 环境,HTTP 客户端和服务器在单独的线程中运行,因此,在单独的事务中运行。在这种情况下,服务器上启动的任何事务都不会回滚。

43.3.1 检测 Web 应用程序类型

如果 Spring MVC 可用,则配置有规则的基于 MVC 的应用程序上下文。如果你只有 Spring WebFlux,我们将检测并配置基于 WebFlux 的应用程序上下文。

如果两者都存在,则优先使用 Spring MVC。如果要在这种情况下测试一个响应式 Web 应用程序,则必须设置 spring.main.web-application-type 属性:

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
public class MyWebFluxTests { ... }

43.3.2 检测测试配置

如果你熟悉 Spring 测试框架,可以使用 @ContextConfiguration(classes=…​) 来指定要加载哪个 Spring @Configuration。或者,你可能经常在测试中使用嵌套的 @Configuration 类。

在测试 Spring Boot 应用程序时,通常不需要这样做。Spring Boot 的 @*Test 注解在未显示定义一个配置时自动搜索主配置。

搜索算法从包含测试的包中起作用,直到找到一个用 @SpringBootApplication 或 @SpringBootConfiguration 注解的类。只要以合理的方式组织代码,通常会找到主要配置。

[Note] Note

如果你使用测试注解来测试应用程序的更具体的部分,则应避免添加在主方法应用程序类上特定区域的配置设置。

@SpringBootApplication 的底层组件扫描配置定义了用于确保切片按预期工作的排除过滤器。如果在 @SpringBootApplication 注解的类中使用显示 @ComponentScan 指令,请注意禁用这些过滤器。如果使用切片,则应再次定义它们。

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

[Note] Note

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

43.3.3 排除测试配置

如果你的应用程序使用组件扫描(例如,如果你使用 @SpringBootApplication 或 @ComponentScan),你可能会发现仅为特定测试创建的顶层配置类意外地被到处拾取。

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

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

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

}
[Note] Note

如果你直接使用 @ComponentScan(也就是说,不通过 @SpringBootApplication),你需要用它注册 TypeExcludeFilter。有关的详细信息请参阅 Javadoc。

43.3.4 使用运行中服务器进行测试

如果需要启动全运行服务器,我们建议你使用随机端口。如果使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT),每次测试运行时都会随机选择可用的端口。

可以使用 @LocalServerPort 注解来在你的测试中注入实际使用的端口。为了方便起见,需要对启动的服务器进行 REST 调用的测试可以附加 @Autowire 一个 WebTestClient,它解析与运行服务器的相对链接,并带有用于验证响应的专用 API,如下面的示例所示:

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.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

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

	@Autowired
	private WebTestClient webClient;

	@Test
	public void exampleTest() {
		this.webClient.get().uri("/").exchange().expectStatus().isOk()
				.expectBody(String.class).isEqualTo("Hello World");
	}

}

Spring Boot 还提供了一个 TestRestTemplate 工具:

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 RandomPortTestRestTemplateExampleTests {

	@Autowired
	private TestRestTemplate restTemplate;

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

}

43.3.5 使用 JMX

当测试上下文框架缓存上下文时,默认情况下禁用 JMX,以防止相同组件在同一域上注册。如果这样的测试需要访问一个 MBeanServer,请考虑将其标记为脏:

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
public class SampleJmxTests {

	@Autowired
	private MBeanServer mBeanServer;

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

}

43.3.6 模拟和发现 Bean

在运行测试时,有时需要在应用程序上下文中模拟某些组件。例如,你可能在开发过程中面对一些不可用的远程服务。当你想模拟在真实环境中可能难以触发的故障时,模拟也是有用的。

Spring Boot 包含一个 @MockBean 注解,可以用来定义 ApplicationContext 的 bean 的 Mockito 模拟。你可以使用注解添加 bean 或替换单个现有 bean 定义。注解可以直接用于测试类、测试中的字段或 @Configuration 类和字段上。当在字段上使用时,也会注入所创建的模拟实例。模拟 bean 在每次测试方法后都会自动复位。

[Note] Note

如果你的测试使用 Spring Boot 的测试注解(例如 @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 来包装任何现有的 bean 和 Mockito spy。所有详细信息请参阅 Javadoc。

[Note] Note

虽然 Spring 的测试框架缓存测试之间的应用上下文,并重用共享相同配置的测试的上下文,但是使用 @MockBean 或 @SpyBean 会影响缓存密钥,这将极有可能增加上下文的数量。

43.3.7 测试自动配置

Spring Boot 的自动配置系统在应用程序中运行良好,但有时对于测试来说可能有点太多。仅加载测试应用程序的切片所需要的配置的部分通常是有帮助的。例如,你可能希望测试 Spring MVC 控制器正确地映射 URL,并且不希望在这些测试中涉及数据库调用,或者你可能想要测试 JPA 实体,并且当这些测试运行时,你对 web 层不感兴趣。

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

[Note] Note

每个切片加载一组非常受限的自动配置类。如果需要排除其中一个,大多数 @…​Test 注解提供了一个排除自动配置属性。或者,你可以使用 @ImportAutoConfiguration#exclude。

[Tip] Tip

还可以使用标准 @SpringBootTest 注解来使用 @AutoConfigure…​ 注解。如果你对切片应用程序不感兴趣,但你需要一些自动配置的测试 bean,则可以使用此组合。

43.3.8 JSON 测试自动配置

若要测试 JSON 对象序列化和反序列化正在按预期的方式工作,可以使用 @JsonTest 注解。@JsonTest 自动配置可用的支持 JSON 映射器,它可能是下列库之一:

  • Jackson ObjectMapper, 任何 @JsonComponent bean 和 任何 Jackson Module
  • Gson
  • Jsonb

如果需要配置自动配置的元素,可以使用 @AutoConfigureJsonTesters 注解。

Spring Boot 包含基于 AssertJ 的助手,这些助手与 JSONassert 和 JsonPath 库一起工作,以检查 JSON 是否按预期的方式出现。JacksonTester、GsonTester、JsonbTester 和 BasicJsonTester 类可以分别用于 Jackson、Gson、Jsonb 和 字符串。在使用 @JsonTest 时,测试类上的任何辅助字段都可以 @Autowired。下面的示例展示了 Jackson 的测试类:

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] Note

JSON 助手类也可以直接用于标准单元测试。要做到这一点,如果你不使用 @JsonTest,请在 @Before 方法之前调用助手的 initFields 方法。

附录中可以找到由 @JsonTest 启用的自动配置列表。

43.3.9 Spring MVC 测试自动配置

为了测试 Spring MVC 控制器是否正常工作,请使用 @WebMvcTest 注解。@WebMvcTest 自动配置 Spring MVC 基础结构,并将扫描 bean 限制为 @Controller、@ControllerAdvice、@JsonComponent、Converter、GenericConverter、Filter、WebMvcConfigurer 和 HandlerMethodArgumentResolver。使用此注解时不扫描正则 @Component bean。

[Tip] Tip

如果需要注册额外组件,例如 Jackson 模块,可以通过在测试中使用 @Import 导入其他配置类。

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

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

[Tip] Tip

你还可以通过用 @AutoConfigureMockMvc 注解非 @WebMvcTest 中的 来自动配置 MockMvc(例如 @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] Tip

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

如果使用 HtmlUnit 或 Selenium,自动配置还提供 HTMLUnit 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] Note

默认情况下,SPring Boot 将 WebDriver bean 放置在一个特殊的范围中,以确保在每次测试之后驱动程序退出,并注入新的实例。如果你不想要这种行为,可以向你的 WebDriver @Bean 定义添加 @Scope("singleton")。

附录中可以找到由 @WebMvcTest 启用的自动配置设置的列表。

[Tip] Tip

有时编写 Spring MVC 测试是不够的;Spring Boot 可以帮助你用一个实际的服务器运行完整的端到端测试。

43.3.10 Spring WebFlux 测试自动配置

为了测试 Spring WebFlux 控制器是否正常工作,你可以使用 @WebFluxTest 注解。@WebFluxTest 自动配置 Spring WebFlux 基础结构,并将扫描 bean 限制为 @Controller、@ControllerAdvice、@JsonComponent、Converter、GenericConverter 和 WebFluxConfigurer。当使用 @WebFluxTest 注解时不扫描正则 @Component bean。

[Tip] Tip

如果需要注册额外组件,如 Jackson Module,你可以在测试中使用 @Import 导入其他配置类。

通常,@WebFluxTest 仅限于单个控制器,并与 @MockBean 注解结合使用,为所需合作者提供模拟实现。

@WebFluxTest 还自动配置 WebTestClient,它提供了一种快速测试 WebFlux 控制器的强大方法,而不需要启动完整的 HTTP 服务器。

[Tip] Tip

你还可以通过 @AutoConfigureWebTestClient 注解来自动配置非 @WebFluxTest 中的 WebTestClient(例如 @SpringBootTest)。下面的示例展示了一个使用 @WebFluxTest 和 WebTestClient 的类:

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

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

	@Autowired
	private WebTestClient webClient;

	@MockBean
	private UserVehicleService userVehicleService;

	@Test
	public void testExample() throws Exception {
		given(this.userVehicleService.getVehicleDetails("sboot"))
				.willReturn(new VehicleDetails("Honda", "Civic"));
		this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)
				.exchange()
				.expectStatus().isOk()
				.expectBody(String.class).isEqualTo("Honda Civic");
	}

}
[Tip] Tip

此设置仅由 WebFlux 应用程序支持,因为使用模拟 web 应用程序的 WebTestClient 只与 WebFlux 一起工作。

附录中可以找到由 @WebFluxTest 启用的自动配置列表。

[Note] Note

@WebFluxTest 不能检测通过函数式 web 框架注册的路由。为了测试上下文中的 RouterFunction bean,考虑通过 @Import 或使用 @SpringBootTest 导入你的路由器功能。

[Tip] Tip

有时编写 Spring WebFlux 测试是不够的;Spring Boot 可以帮助你用实际的服务器运行完整的端到端测试。

43.3.11 数据 JPA 测试自动配置

可以使用 @DataJpaTest 注解来测试 JPA 应用程序。默认情况下,它配置内存中的嵌入式数据库,扫描 @Entity 类,并配置 Spring Data JPA 库,正则 @Component bean 不会加载到 ApplicationContext 中。

默认情况下,数据 JPA 测试是事务性的,并在每次测试结束时回滚。更多详细内容请参阅 Spring Framework 参考文档中的相关部分。如果这不是你想要的,可以禁用测试或整个类的事务管理,如下:

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 的替代。如果你想在 @DataJpaTest 实例之外使用 TestEntityManager,还可以使用 @AutoConfigureTestEntityManager 注解。如果需要的话,也可以使用 JdbcTemplate。下面的示例展示了使用的 @DataJpaTest 注解。

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

}

在内存中,嵌入式数据库通常对测试工作的很好,因为它们速度快,不需要任何安装。但是,如果你喜欢对真实数据库运行测试,则可以使用 注解,如下面的示例所示:

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

	// ...

}

附录中可以找到由 @DataJpaTest 启用的自动配置设置的列表。

43.3.12 JDBC 测试自动配置

@JdbcTest 类似于 @DataJpaTest,但是它用于纯 JDBC 相关测试。默认情况下,它还配置内存中的嵌入式数据库和 JdbcTemplate。正则 @Component bean 不会加载到 ApplicationContext。

默认情况下,JDBC 测试都是事务性的,在每次测试结束时都会回滚。更多详细信息请参阅 Spring Framework 参考文档中的相关部分。如果这不是你想要的,可以禁用测试的或整个类的事务管理,如下:

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 {

}

如果你希望测试与实际数据库运行,那么可以使用与 DataJpaTest 相同的方式使用 @AutoConfigureTestDatabase 注解。(详见小节 43.3.11, “JPA 数据测试自动配置”)

附录中可以找到由 @JdbcTest启用的自动配置列表。

43.3.13 jOOQ 测试自动配置

你可以以 @JdbcTest 类似的方式使用 @JooqTest,只是是用于 jOOQ 相关的测试。由于 jOOQ 很大程度上依赖于与数据库集合相对应的基于 Java 的集合,因此使用现有的数据源。如果要使用内存数据库来替代它,可以使用 @AutoConfigureTestDatabase 来重写这些设置。(关于使用 Spring Boot 的 jOOQ 的更多信息,请参阅本章前面的小节 29.5, “使用 jOOQ”)

@JooqTest 配置了一个 DSLContext。正则 @Component bean 不会加载到 ApplicationContext 中。下面的示例展示了使用的 @JooqTest:

import org.jooq.DSLContext;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.jooq.JooqTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@JooqTest
public class ExampleJooqTests {

	@Autowired
	private DSLContext dslContext;
}

JOOQ 测试是事务性的,在每次测试结束时都会回滚。如果这不是你想要的,可以禁用测试的或整个测试类的事务管理,如 JDBC 示例中所示。

附录中可以找到由 @JooqTest 启用的自动配置列表。

43.3.14 MongoDB 数据测试自动配置

可以使用 @DataMongoTest 来测试 MongoDB 应用程序。默认情况下,它配置内存中的嵌入式 MongoDB(如果可用),配置一个 MongoTemplate,扫描 @Document 类,并配置 Spring Data MongoDB 库。正则 @Component bean 不会加载到 ApplicationContext 中。(有关使用 Spring Boot 的 MongoDB 的更多信息,请参阅本章前面的 小节 30.2, “MongoDB”)

下面的类展示了使用中的 @DataMongoTest 注解:

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 启用的自动配置设置的列表。

43.3.15 Neo4j 数据测试自动配置

可以使用 @DataNeo4jTest 来测试 Neo4j 应用程序。默认情况下,它使用内存中的嵌入式 Neo4j(如果嵌入式驱动程序可用),扫描 @NodeEntity 类,并配置 Spring Data Neo4j 库。正则 @Component bean 不会加载到 ApplicationContext 中。(关于使用 Spring Boot 的 Neo4J 的更多信息,请参阅本章前面的 小节 30.3, “Neo4j”)

下面的示例展示了在 Spring Boot 中使用 Neo4J 测试的典型设置:

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataNeo4jTest
public class ExampleDataNeo4jTests {

	@Autowired
	private YourRepository repository;

	//
}

默认情况下,Neo4j 数据测试是事务性的,并且在每次测试结束时回滚。更多详细信息请参阅 Spring Framework 参考文档中的相关部分。如果这不是你想要的,可以禁用测试的或整个类的事务管理,如下:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

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

}

附录中可以找到由 @DataNeo4jTest 启用的自动配置设置的列表。

43.3.16 Redis 数据测试自动配置

可以使用 @DataRedisTest 来测试 Redis 应用程序。默认情况下,它扫描 @RedisHash 类并配置 Spring Data Redis 库。正则 @Component bean 不会加载到 ApplicationContext 中。(有关 Spring Boot 的 Redis 的更多信息,请参阅本章前面的小节 30.1, “Redis”)

下面的示例展示了使用的 @DataRedisTest 注解:

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataRedisTest
public class ExampleDataRedisTests {

	@Autowired
	private YourRepository repository;

	//
}

附录中可以找到由 @DataRedisTest 启用的自动配置设置的列表。

43.3.17 LDAP 数据测试自动配置

可以使用 @DataLdapTest 来测试 LDAP 应用程序。默认情况下,它配置内存中嵌入式 LDAP(如果可用),配置 LdapTemplate,扫描 @Entry 类,并配置 Spring Data LDAP 库。正则 @Component bean 不会加载到 ApplicationContext 中。(有关使用 Spring Boot 的 LDAP 的更多信息,请参阅本章前面的小节 30.9, “LDAP”)

下面的示例展示了使用中的 @DataLdapTest 注解:

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataLdapTest
public class ExampleDataLdapTests {

	@Autowired
	private LdapTemplate ldapTemplate;

	//
}

在内存中的嵌入式 LDAP 通常对测试工作很好,因为它很快,不需要任何开发人员安装。但是,如果你喜欢对真正的 LDAP 服务器进行测试,则应该排除嵌入式 LDAP 自动配置,如下面的示例所示:

import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
public class ExampleDataLdapNonEmbeddedTests {

}

附录中可以找到由 @DataLdapTest 自动配置设置的列表。

43.3.18 REST 客户端自动配置

可以使用 @RestClientTest 注解来测试 REST 客户端。默认情况下,它自动配置 Jackson、GSON 和 Jsonb 支持,配置 RestTemplateBuilder,并添加对 MockRestServiceServer 的支持。要测试的特定 bean 应该使用 @RestClientTest 的 value 或 components 属性来指定,如下面的示例所示:

@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 启用的自动配置设置的列表。

43.3.19 Spring REST Docs 测试自动配置

你可以使用 @AutoConfigureRestDocs 注解来在使用 Mock MVC 或 REST Assured 的测试中使用 Spring REST Docs。它消除了对 Spring REST Docs 中 JUnit 规则的需求。

@AutoConfigureRestDocs 可用于重写默认输出目录(如果使用 Maven 是target/generated-snippets,或使用 Gradle 是build/generated-snippets)。它还可以用来配置在任何文档 URI 中出现的 host、scheme、或 port。

使用 Mock MVC 的 Spring REST Docs 测试自动配置

@AutoConfigureRestDocs 自定义 MockMvc bean 来使用 Spring REST Docs。你可以使用 @Autowired 来注入它,并在使用 Mock MVC 和 Spring REST Docs 时,在你的测试中使用它,如下面的示例所示:

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

}

如果你需要更多对 Spring REST Docs 配置的控制,而不是 @AutoConfigureRestDocs 的属性提供的,那么你可以使用 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 调用自动生产默认代码段。下面的示例展示了 RestDocumentationResultHandler 的定义:

@TestConfiguration
static class ResultHandlerConfiguration {

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

}

使用 REST Assured 的 Spring REST Docs 测试自动配置

@AutoConfigureRestDocs 使 RequestSpecification bean 预先配置为使用 Spring REST Docs,可用于你的测试。你可以使用 @Autowired 注入它,并在你的测试中使用它,正如你通常在使用 REST Assured 和 Spring REST Docs 所使用的,如下面的示例所示:

import io.restassured.specification.RequestSpecification;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document;

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

	@LocalServerPort
	private int port;

	@Autowired
	private RequestSpecification documentationSpec;

	@Test
	public void listUsers() {
		given(this.documentationSpec).filter(document("list-users")).when()
				.port(this.port).get("/").then().assertThat().statusCode(is(200));
	}

}

如果你需要更多的对 Spring REST Docs 配置的控制,而不是由 @AutoConfigureRestDocs 的属性提供的,那么可以使用 RestDocsRestAssuredConfigurationCustomizer bean,如下面的示例所示:

@TestConfiguration
public static class CustomizationConfiguration
		implements RestDocsRestAssuredConfigurationCustomizer {

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

}

43.3.20 用户配置和切片

如果以合理的方式构造代码,你的 @SpringBootApplication 类将默认作为测试的配置使用。

因此,重要的是不要用特定于其函数式特定区域的配置设置来丢弃应用程序的主类。

假设你正在使用 Spring Batch,并且依赖于它的自动配置。你可以把你的 @SpringBootApplication 定义如下:

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

因为这个类是测试的源配置,所以任何切片测试实际上都试图启动 Spring Batch,这绝对不是你想要做的。推荐的方法是将特定于区域的配置移动到与应用程序相同级别的单独的 @Configuration 类,如下面的示例所示:

@Configuration
@EnableBatchProcessing
public class BatchConfiguration { ... }
[Note] Note

根据应用程序的复杂性,你可以为你的自定义设置一个单独的 @Configuration 类,也可以为每个域区域设置一个类。后一种方法允许您在其中一个测试中启用它,如果需要的话,可以使用 @Import 注解。

混乱的另一个来源是类路径扫描。假设在以合理的方式构造代码时,需要扫描一个附加的包。你的应用程序可能类似于以下代码:

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

这样做,只扫描指定的这两个包,而不考虑你选择的切面,有效地覆盖了默认的组件扫描指令带来的副作用。例如,@DataJpaTest 似乎会扫描应用程序的组件和用户配置。另外,将自定义指令移动到单独的类是解决此问题的好方法。

[Tip] Tip

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

43.3.21 使用 Spock 来测试 Spring Boot 应用程序

如果你希望使用 Spock 来测试 Spring Boot 应用程序,则应该将 Spock 的 spock-spring 模块的依赖项添加应用程序的构建中。spock-spring 将 Spring 的测试框架集成到 Spock 中。建议你使用 Spock 1.1 或更高版本来受益于对 Spock 的 Spring Framework 和 Spring Boot 集成的一些改进。有关的详细信息请参阅 Spock 的 Spring 模块的文档。

43.4 单元测试

在测试应用程序时通常有用的几个测试实用程序类被打包为 spring-boot 的一部分。

43.4.1 ConfigFileApplicationContextInitializer

ConfigFileApplicationContextInitializer 是一个 ApplicationContextInitializer,可以应用到你的测试中加载 Spring Boot application.properties 文件。当你不需要由 @SpringBootTest 提供的全部特性时,你可以实用它,如下面的示例所示:

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

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

43.4.2 TestPropertyValues

TestPropertyValues 允许你快速将属性添加到 ConfigurableEnvironment 或 ConfigurableApplicationContext。你可以使用 key=value 字符串调用它,如下:

TestPropertyValues.of("org=Spring", "name=Boot").applyTo(env);

43.4.3 OutputCapture

OutputCapture 是一个 JUnit Rule,你可以使用它捕获 System.out 和 System.err 输出。你可以声明捕获声明为 @Rule,然后使用断言的 toString(),如下:

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

}

43.4.4 TestRestTemplate

[Tip] Tip

Spring Framework 5.0 提供了一个新的 WebTestClient,用于 WebFlux 集成测试,以及 WebFlux 和 MVC 端到端测试。它与断言模版不同,提供了断言的 fluent API。

TestRestTemplate 是 Spring 的 RestTemplate 的一种方便选择,它在集成测试中是有用的。你可以得到一个普通的模版或一个发送基本 HTTP 身份验证(用户名和密码)的模版。在这两种情况下,模版都是以测试友好的方式在服务器端错误上不抛出异常。建议使用 Apache HTTP Client (version 4.3.2 或更高),但不是强制性的。如果在类路径上有这个,TestRestTemplate 通过适当配置客户端进行响应。如果你确实使用 Apache 的 HTTP 客户端,则启用了一些附加的测试友好特性:

  • 不遵循重定向(因此,你可以声明响应位置)。
  • 忽略 Cookies(因此模板是无状态的)。

TestRestTemplate 可以直接在你的集成测试中实例化,如下面的示例所示:

public class MyTest {

	private TestRestTemplate template = new TestRestTemplate();

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

}

或者,如果你使用 WebEnvironment.RANDOM_PORT 或 WebEnvironment.DEFINED_PORT 的 @SpringBootTest 注解,可以注入完全配置的 TestRestTemplate 并开始使用它。如果需要,可以通过 RestTemplateBuilder bean 应用其它自定义。不指定主机和端口的 URL 自动连接到嵌入式服务器,如下面的示例所示:

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

	@Autowired
	private TestRestTemplate template;

	@Test
	public void testRequest() {
		HttpHeaders headers = this.template.getForEntity("/example", String.class)
				.getHeaders();
		assertThat(headers.getLocation()).hasHost("other.example.com");
	}

	@TestConfiguration
	static class Config {

		@Bean
		public RestTemplateBuilder restTemplateBuilder() {
			return new RestTemplateBuilder().setConnectTimeout(1000).setReadTimeout(1000);
		}

	}

}

44. WebSockets

Spring Boot 为嵌入式 Tomcat、Jetty 和 Undertow 提供了 WebSockets 自动配置。如果将 war 文件部署到独立容器,Spring Boot 假定容器负责其 WebSocket 支持的配置。

Spring Framework 提供了丰富的 WebSocket 支持,可以通过 spring-boot-starter-websocket 模块轻松访问。

45. Web Services

Spring Boot 提供 Web Services 自动配置,因此你必须做的就是定义 Endpoints。

Spring Web Services 特性可以很容易地被 spring-boot-starter-webservices 访问。

可以分别为 WSDL 和 XSD 自动创建 SimpleWsdl11Definition 和 SimpleXsdSchema bean。为此,配置它们的位置,如下面的示例所示:

spring.webservices.wsdl-locations=classpath:/wsdl

46. 使用 WebServiceTemplate 调用 Web Services

如果你需要从应用程序调用远程 Web services,可以使用 WebServiceTemplate 类。由于 WebServiceTemplate 实例通常需要在使用之前进行定制,Spring Boot 不提供任何单个自动配置的 WebServiceTemplate bean。但是,它确实自动配置 WebServiceTemplateBuilder,它可以用来在需要时创建 WebServiceTemplate 实例。

下面的代码展示了一个典型的例子:

@Service
public class MyService {

	private final WebServiceTemplate webServiceTemplate;

	public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {
		this.webServiceTemplate = webServiceTemplateBuilder.build();
	}

	public DetailsResp someWsCall(DetailsReq detailsReq) {
		 return (DetailsResp) this.webServiceTemplate.marshalSendAndReceive(detailsReq, new SoapActionCallback(ACTION));

	}

}

默认情况下,WebServiceTemplateBuilder 使用类路径上可用的 HTTP 客户端库来检测一个合适的基于 HTTP 的 WebServiceMessageSender。你还可以自定义读取和连接超时,如下:

@Bean
public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {
	return builder.messageSenders(new HttpWebServiceMessageSenderBuilder()
			.setConnectTimeout(5000).setReadTimeout(2000).build()).build();
}