本指南将引导您完成创建一个 Spring 应用程序并使用 JUnit 进行测试的过程。
您将构建什么
您将构建一个简单的 Spring 应用程序并使用 JUnit 进行测试。您可能已经知道如何编写和运行应用程序中各个类的单元测试,因此在本指南中,我们将重点介绍如何使用 Spring Test 和 Spring Boot 功能来测试 Spring 与您的代码之间的交互。您将从测试应用程序上下文成功加载的简单测试开始,然后继续使用 Spring 的 MockMvc
仅测试 Web 层。
您需要什么
-
大约 15 分钟
-
一个常用的文本编辑器或IDE
-
Java 1.8 或更高版本
-
您也可以直接将代码导入到您的 IDE 中:
如何完成本指南
与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者也可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都能获得可运行的代码。
要从头开始,请继续阅读从 Spring Initializr 开始。
要跳过基础知识,请执行以下操作:
-
下载并解压本指南的源代码仓库,或者使用 Git 克隆它:
git clone https://github.com/spring-guides/gs-testing-web.git
-
进入
gs-testing-web/initial
目录 -
跳转到 创建一个简单的应用程序。
完成后,您可以对照gs-testing-web/complete
中的代码检查您的结果。
从 Spring Initializr 开始
您可以使用这个预初始化项目并点击生成以下载一个ZIP文件。该项目已配置为适合本教程中的示例。
要手动初始化项目:
-
访问 https://start.spring.io。该服务会为您拉取应用程序所需的所有依赖项,并完成大部分的设置工作。
-
选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。
-
点击 Dependencies 并选择 Spring Web。
-
点击 Generate。
-
下载生成的 ZIP 文件,这是一个根据您的选择配置好的 Web 应用程序的压缩包。
如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。
您也可以从 Github 上 fork 该项目,并在您的 IDE 或其他编辑器中打开它。
创建一个简单的应用程序
为您的 Spring 应用程序创建一个新的控制器。以下代码(来自 src/main/java/com/example/testingweb/HomeController.java
)展示了如何实现:
package com.example.testingweb;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@RequestMapping("/")
public @ResponseBody String greeting() {
return "Hello, World";
}
}
前面的示例没有指定
GET
、PUT
、POST
等操作。默认情况下,@RequestMapping
会映射所有的 HTTP 操作。您可以使用@GetMapping
或@RequestMapping(method=GET)
来缩小映射范围。
运行应用程序
Spring Initializr 会为您创建一个应用程序类(一个包含 main()
方法的类)。在本指南中,您无需修改此类。以下代码(来自 src/main/java/com/example/testingweb/TestingWebApplication.java
)展示了 Spring Initializr 创建的应用程序类:
package com.example.testingweb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestingWebApplication {
public static void main(String[] args) {
SpringApplication.run(TestingWebApplication.class, args);
}
}
@SpringBootApplication
是一个便捷的注解,它包含了以下所有内容:
-
@Configuration
: 将该类标记为应用程序上下文的bean定义来源。 -
@EnableAutoConfiguration
: 告诉Spring Boot根据类路径设置、其他bean和各种属性设置开始添加bean。 -
@EnableWebMvc
: 将应用程序标记为Web应用程序,并激活关键行为,例如设置DispatcherServlet
。当Spring Boot在类路径上看到spring-webmvc
时,它会自动添加此注解。 -
@ComponentScan
: 告诉Spring在包含注解的TestingWebApplication
类所在的包(com.example.testingweb
)中查找其他组件、配置和服务,使其能够找到com.example.testingweb.HelloController
。
main()
方法使用 Spring Boot 的 SpringApplication.run()
方法来启动应用程序。您是否注意到没有一行 XML 代码?也没有 web.xml
文件。这个 Web 应用程序是 100% 纯 Java 的,您无需处理任何配置管道或基础设施。Spring Boot 为您处理了所有这些。
日志输出会显示出来。服务应该在几秒钟内启动并运行。
测试应用程序
现在应用程序已经运行起来了,您可以对其进行测试。您可以在 http://localhost:8080
加载主页。不过,为了在您进行更改时更有信心确保应用程序正常工作,您需要自动化测试。
Spring Boot 假定您计划测试您的应用程序,因此它会将必要的依赖项添加到您的构建文件(
build.gradle
或pom.xml
)中。
首先,您可以编写一个简单的健全性检查测试,如果应用程序上下文无法启动,该测试将失败。下面的代码清单(来自src/test/java/com/example/testingweb/TestingWebApplicationTest.java
)展示了如何做到这一点:
package com.example.testingweb;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TestingWebApplicationTests {
@Test
void contextLoads() {
}
}
@SpringBootTest
注解告诉 Spring Boot 寻找一个主配置类(例如带有 @SpringBootApplication
的类),并使用它来启动 Spring 应用程序上下文。您可以在 IDE 中或命令行上运行此测试(通过运行 ./mvnw test
或 ./gradlew test
),并且它应该通过。为了确认上下文正在创建您的控制器,您可以添加一个断言,如下例所示(来自 src/test/java/com/example/testingweb/SmokeTest.java
):
package com.example.testingweb;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SmokeTest {
@Autowired
private HomeController controller;
@Test
void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
Spring 解释 @Autowired
注解,并且在测试方法运行之前注入控制器。我们使用 AssertJ(它提供了 assertThat()
和其他方法)来表达测试断言。
Spring Test 支持的一个很好的特性是应用程序上下文在测试之间会被缓存。这样一来,如果测试用例中有多个方法或多个具有相同配置的测试用例,它们只需支付一次启动应用程序的开销。您可以通过使用 @DirtiesContext 注解来控制缓存。
进行完整性检查固然不错,但您还应该编写一些测试来验证应用程序的行为。为此,您可以启动应用程序并监听连接(就像在生产环境中那样),然后发送一个 HTTP 请求并验证响应。以下代码片段(来自 src/test/java/com/example/testingweb/HttpRequestTest.java
)展示了如何做到这一点:
package com.example.testingweb;
import org.junit.jupiter.api.Test;
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.beans.factory.annotation.Value;
import org.springframework.boot.test.web.server.LocalServerPort;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class HttpRequestTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
String.class)).contains("Hello, World");
}
}
请注意使用 webEnvironment=RANDOM_PORT
来启动服务器并分配一个随机端口(这在测试环境中非常有用,可以避免端口冲突),以及通过 @LocalServerPort
注入端口。另外,需要注意的是,Spring Boot 已经自动为您提供了一个 TestRestTemplate
,您只需添加 @Autowired
即可使用。
另一个有用的方法是完全不启动服务器,而是仅测试 Spring 处理传入的 HTTP 请求并将其传递给控制器的层。这样,几乎整个堆栈都会被使用,您的代码将以与处理实际 HTTP 请求完全相同的方式被调用,但无需承担启动服务器的开销。要做到这一点,可以使用 Spring 的 MockMvc
,并通过在测试用例上使用 @AutoConfigureMockMvc
注解来请求注入它。以下代码清单(来自 src/test/java/com/example/testingweb/TestingWebApplicationTest.java
)展示了如何实现这一点:
package com.example.testingweb;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
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.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
class TestingWebApplicationTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldReturnDefaultMessage() throws Exception {
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, World")));
}
}
在这个测试中,完整的 Spring 应用上下文被启动,但没有启动服务器。我们可以通过使用 @WebMvcTest
将测试范围缩小到仅测试 Web 层,如下面的代码清单(来自 src/test/java/com/example/testingweb/WebLayerTest.java
)所示:
@WebMvcTest
include::complete/src/test/java/com/example/testingweb/WebLayerTest.java
测试断言与之前的情况相同。然而,在这个测试中,Spring Boot 只会实例化 Web 层,而不是整个上下文。在具有多个控制器的应用程序中,您甚至可以请求只实例化其中一个控制器,例如使用 @WebMvcTest(HomeController.class)
。
到目前为止,我们的 HomeController
非常简单,没有任何依赖项。我们可以通过引入一个额外的组件来存储问候语(可能在一个新的控制器中),使其更加真实。以下示例(来自 src/main/java/com/example/testingweb/GreetingController.java
)展示了如何实现这一点:
package com.example.testingweb;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class GreetingController {
private final GreetingService service;
public GreetingController(GreetingService service) {
this.service = service;
}
@RequestMapping("/greeting")
public @ResponseBody String greeting() {
return service.greet();
}
}
然后创建一个问候服务,如下面的代码清单所示(来自 src/main/java/com/example/testingweb/GreetingService.java
):
package com.example.testingweb;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public String greet() {
return "Hello, World";
}
}
Spring 会自动将服务依赖注入到控制器中(由于构造函数的签名)。以下代码片段(来自 src/test/java/com/example/testingweb/WebMockTest.java
)展示了如何使用 @WebMvcTest
来测试该控制器:
package com.example.testingweb;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(GreetingController.class)
class WebMockTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private GreetingService service;
@Test
void greetingShouldReturnMessageFromService() throws Exception {
when(service.greet()).thenReturn("Hello, Mock");
this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, Mock")));
}
}
我们使用 @MockBean
来创建并注入一个 GreetingService
的模拟对象(如果不这样做,应用程序上下文将无法启动),并使用 Mockito
设置其预期行为。
总结
恭喜!您已经开发了一个 Spring 应用程序,并使用 JUnit 和 Spring MockMvc
对其进行了测试,还使用了 Spring Boot 来隔离 Web 层并加载特定的应用程序上下文。
另请参阅
以下指南可能也会有所帮助: