下一篇[未完待续]
46.3、测试 Spring Boot 应用程序
Spring Boot 应用程序是 Spring ApplicationContext,因此除了通常使用普通的 Spring 上下文所做的事情之外,不必做任何特别的事情来测试它。
注释:默认情况下,只有在使用 SpringApplication 创建 Spring Boot 时,后者的外部属性、日志记录和其他功能才会安装在上下文中。
Spring Boot 提供了一个 @SpringBootTest 注解,当你需要 Spring Boot 功能时,它可以作为标准 spring-test @ContextConfiguration 注解的替代。该注解的工作方式是通过 SpringApplication 创建测试中使用的 ApplicationContext。除了 @SpringBootTest 之外,还提供了许多其他注解来测试应用程序更具体的切片。
提示:如果你正在使用 JUnit 4,不要忘记在你的测试中添加 @RunWith(sprint grunner.class),否则注解将被忽略。如果你使用的是 JUnit 5,则无需添加等效的 @ExtendWith(SpringExtension.class) 作为 @SpringBootTest,其他 @… 测试注解已经用它注解了。
默认情况下,@SpringBootTest 不会启动服务器。你可以使用 @SpringBootTest 的 webEnvironment 属性来进一步优化测试的运行方式:
(1)MOCK(默认):加载 web ApplicationContext 并提供模拟 web 环境。使用此注解时,嵌入式服务器不会启动。如果你的类路径上没有可用的 web 环境,则此模式将透明地返回到创建常规的非 web ApplicationContext。它可以与 @AutoConfigureMockMvc 或 @AutoConfigureWebTestClient 结合使用,用于 web 应用程序的基于模拟的测试。
(2)RANDOM_PORT:加载 WebServerApplicationContext 并提供一个真实的 web 环境。嵌入式服务器在随机端口上启动和监听。
(3)DEFINED_PORT:加载 WebServerApplicationContext 并提供一个真实的 web 环境。嵌入式服务器在一个定义的端口(来自 application.properties)或默认端口 8080 上启动和侦听。
(4)NONE:通过使用 SpringApplication 加载 ApplicationContext,但不提供任何 web 环境(mock 或其他)。
注释:如果你的测试是 @Transaction,则默认情况下,它会在每个测试方法结束时回滚事务。然而,由于对 RANDOM_PORT 或 DEFINED_PORT 使用这种安排隐式地提供了一个真正的 servlet 环境,所以 HTTP 客户端和服务器在单独的线程中运行,从而运行在单独的事务中。在这种情况下,服务器上启动的任何事务都不会回滚。
注释:如果你的应用程序为管理服务器使用不同的端口,那么使用 webEnvironment = WebEnvironment.RANDOM_PORT 的 @SpringBootTest 也将在一个单独的随机端口上启动管理服务器。
46.3.1、检测 Web 应用程序类型
如果 Spring MVC 可用,则配置一个常规的基于 MVC 的应用程序上下文。如果你只有 Spring WebFlux,我们将检测它并配置一个基于 WebFlux 的应用程序上下文。
如果两者都存在,则 Spring MVC 优先。如果要在这个场景中测试反应式 web 应用程序,则必须设置 spring.main.web-application-type 属性:
<code>@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
public class MyWebFluxTests { ... }/<code>
46.3.2、检测测试配置
如果你熟悉 Spring Test Framework,那么可能习惯于使用 @ContextConfiguration(classes=…) 来指定要加载哪个 Spring @Configuration。或者,你可能经常在测试中使用嵌套的 @Configuration 类。
在测试 Spring Boot 应用程序时,通常不需要这样做。当你没有明确定义主配置时,Spring Boot 的 @*Test 注解会自动搜索主配置。
搜索算法从包含测试的包开始工作,直到找到一个用 @SpringBootApplication 或 @SpringBootConfiguration 注解的类。只要你以合理的方式构建代码,你的主要配置通常会被找到。
注解:
如果你使用测试注释来测试应用程序的更具体的切片,则应避免在主方法的应用程序类上添加特定于特定区域的配置设置。
@SpringBootApplication 的底层组件扫描配置定义了用于确保切片按预期工作的排除过滤器。如果在基于注解 @SpringBootApplication 的类上使用显式的 @ComponentScan 指令,请注意这些过滤器将被禁用。如果使用切片,则应重新定义它们。
如果要自定义主配置,可以使用嵌套的 @TestConfiguration 类。与嵌套的 @Configuration 类(将用于替代应用程序的主配置)不同,嵌套的 @TestConfiguration 类是在应用程序的主配置之外使用的。
注释:Spring 的测试框架在测试之间缓存应用程序上下文。因此,只要你的测试共享相同的配置(无论如何发现),加载上下文的潜在耗时过程只会发生一次。
46.3.3、排除测试配置
如果你的应用程序使用组件扫描(例如,如果你使用 @SpringBootApplication 或 @ComponentScan),你可能会发现仅为特定测试创建的顶级配置类会意外地在任何地方出现。
如前所述,@TestConfiguration 可用于测试的内部类,以自定义主配置。当放置在顶级类上时,@TestConfiguration 表示 src/test/java 中的类不应该通过扫描来获取。然后,可以在需要时显式导入该类,如下例所示:
<code>@RunWith(SpringRunner.class)
@SpringBootTest
@Import(MyTestsConfiguration.class)
public class MyTests {
@Test
public void exampleTest() {
...
}
}/<code>
注释:如果你直接使用 @ComponentScan (也就是说,不是通过 @SpringBootApplication),你需要用它注册 TypeExcludeFilter。有关详细信息,请参见 Javadoc。(https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/api/org/springframework/boot/context/TypeExcludeFilter.html )
46.3.4、使用模拟环境进行测试
默认情况下,@SpringBootTest 不启动服务器。如果你有要针对此模拟环境进行测试的 web 端点,则可以另外配置 MockMvc,如以下示例所示:
<code>import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {
@Autowired
private MockMvc mvc;
@Test
public void exampleTest() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
}
}/<code>
提示:如果你只想关注 web 层而不启动完整的 ApplicationContext,请考虑使用 @WebMvcTest。
或者,你可以配置 WebTestClient,如下面示例所示:
<code>import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebTestClient
public class MockWebTestClientExampleTests {
@Autowired
private WebTestClient webClient;
@Test
public void exampleTest() {
this.webClient.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class)
.isEqualTo("Hello World");
}
}
/<code>
46.3.5、使用正在运行的服务器进行测试
如果你需要启动完全运行的服务器,我们建议你使用随机端口。如果使用 @SpringBootTest (webEnvironment=WebEnvironment.RANDOM_PORT),则每次测试运行时都会随机选择一个可用端口。
@LocalServerPort 注解可用于将实际使用的端口注入测试。为了方便起见,需要对启动的服务器进行 REST 调用的测试还可以 @Autowire 一个 WebTestClient,它解析到正在运行的服务器的相关链接,并附带用于验证响应的专用 API,如下面示例所示:
<code>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");
}
}
/<code>
此设置需要类路径上的 spring-webflux。如果你不能或不想添加 webflux,Spring Boot 还提供了一个 TestRestTemplate 工具:
<code>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");
}
}
/<code>
下一篇[未完待续]
閱讀更多 IT薺薺菜 的文章