共计 4267 个字符,预计需要花费 11 分钟才能阅读完成。
原文 https://reflectoring.io/sprin…
翻译: 祝坤荣
在这个测试 Spring Boot 系列的第二局部,咱们来看下 web contoller。开始,咱们会摸索下 web controller 到底做了什么,而后咱们能够基于写单元测试来笼罩所有它的职责。
而后,咱们来看看如果在测试用笼罩这些职责。只有当所有这些职责都被笼罩到了,咱们才能够必定咱们的 contoller 的行为应该与线上环境一样了。
样例代码
这篇文章提供在 GitHub 上的可运行代码。
测试 Spring Boot 系列
这篇教程是一个系列的一部分:
- Spring Boot 的单元测试
- 应用 @WebMvcTest 测试 Spring Boot 的 MVC Web Controller
- 应用 @DataJpaTest 测试 Spring Boot 的 JPA Queries
- 应用 @SpringBootTest 进行集成测试
如果你喜爱看视频学习,能够看看 Philip 的测试 Spring Boot 利用课程(如果你通过这个链接购买,我有分成)。
依赖
咱们会应用 JUnit Jupiter(JUnit 5)作为测试框架,Mockito 来模仿,AssertJ 来建设断言,Lombok 来缩小冗余代码:
dependencies {compile('org.springframework.boot:spring-boot-starter-web')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile 'org.junit.jupiter:junit-jupiter-engine:5.2.0'
testCompile('org.mockito:mockito-junit-jupiter:2.23.0')
}
AssertJ 和 Mockito 会通过引入 spring-boot-starter-test 主动引入。
Web Controller 的职责
让咱们先看一个典型的 REST controller:
@RequiredArgsConstructor
class RegisterRestController {
private final RegisterUseCase registerUseCase;
@PostMapping("/forums/{forumId}/register")
UserResource register(@PathVariable("forumId") Long forumId,
@Valid @RequestBody UserResource userResource,
@RequestParam("sendWelcomeMail") boolean sendWelcomeMail) {
User user = new User(userResource.getName(),
userResource.getEmail());
Long userId = registerUseCase.registerUser(user, sendWelcomeMail);
return new UserResource(
userId,
user.getName(),
user.getEmail());
}
}
Controller 的办法通过 @PostMapping 的申明来定义须要监听的 URL,HTTP 办法和 content 类型。
它承受通过 @PathVariable, @RequestBody 和 @RequestsParam 申明的入参,其被进入的 HTTP 申请主动填充。
参数可能被申明成 @Valid 来表明 Spring 须要对它们进行 bean 校验。
而后 controller 应用这些参数,调用业务逻辑,失去一个简略 Java 对象,其会被以 JSON 的模式默认主动写入到 HTTP 响应 body 中。
这里有很多 Spring 魔法。简略来说,对每一个申请,controller 通常通过以下步骤:
# | 职责 | 形容 |
---|---|---|
1. | 监听 HTTP 申请 | controller 须要对特定的 URL,HTTP 办法和 content 类型做响应 |
2. | 反序列化输出 | controller 须要解析进入的 HTTP 申请并从 URL,HTTP 申请参数和申请 body 中创立 Java 对象,这样咱们在代码中应用 |
3. | 查看输出 | controller 是进攻不非法输出的第一道防线,所以这是个校验输出的好中央 |
4. | 调用业务逻辑 | 失去了解析过的入参,controller 须要将入参传给业务逻辑冀望的业务模型 |
5. | 序列化输入 | controller 失去业务逻辑的输入并将其序列化到 HTTP 响应中 |
6. | 翻译异样 | 如果某些中央有异样产生了,controller 须要将其翻译成一个正当的谬误音讯和 HTTP 状态码 |
所以 controller 有一大堆活要干!
咱们要留神不要再填加更多的像执行业务逻辑这样的职责了。那样的话,咱们的 controller 测试会过于冗余并难以保护。
咱们如果编写能够笼罩所有这些职责的正当测试呢?
单元或集成测试?
咱们是写单元测试?还是写集成测试呢?这两个有什么不同?让咱们看看两种形式并抉择其中一个。
在单元测试中,咱们须要将 controller 隔离测试。这示意咱们要初始化一个 controller 对象,对业务逻辑进行模仿,而后调用 controller 的办法并校验返回。
这在咱们的例子里行吗?让咱们看下在下面咱们定义的 6 个职责是否在隔离的单元测试中笼罩:
# | 职责 | 形容 |
---|---|---|
1. | 监听 HTTP 申请 | 不行,因为单元测试不会查看 @PostMapping 申明并模仿 HTTP 申请的特定参数 |
2. | 反序列化输出 | 不行,因为像 @RequestParam 和 @pathVariable 这样的申明不会被测验。咱们会以 Java 对象的模式提供输出,这会跳过 HTTP 申请的反序列化 |
3. | 查看输出 | 不行,不依赖 bean 校验,因为 @Valid 申明不会被校验。 |
4. | 调用业务逻辑 | 能够,因为咱们能够校验业务逻辑被冀望的参数调用 |
5. | 序列化输入 | 不行,因为只能校验 Java 版本的输入,HTTP 返回不会生成 |
6. | 翻译异样 | 不行,咱们能够查看一个特定的异样是否产生,但它不会被翻译成一个 JSON 返回或 HTTP 状态码 |
简略来说,一个简略的单元测试不能笼罩 HTTP 层。所以咱们要将 Spring 引入到测试中来帮咱们做点 HTTP 魔法。因而,咱们构建一个集成测试来测试咱们 controller 代码与 Spring 提供的 HTTP 反对组件的集成。
一个 Spring 集成测试启动一个 Spring 蕴含所有咱们须要 bean 的利用上下文。这包含了负责监听特定 URL,序列化与反序列化 JSON 并翻译 HTTP 异样的框架 bean。这些 bean 会查看在一个简略的单元测试里会被疏忽的申明。
那么,咱们怎么做呢?
应用 @WebMvcTest 校验 Controller 的职责
Spring Boot 提供了 @WebMvcTest 申明来加载只包含了须要测试 web controller 的 bean 的利用上下文:
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = RegisterRestController.class)
class RegisterRestControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private RegisterUseCase registerUseCase;
@Test
void whenValidInput_thenReturns200() throws Exception {mockMvc.perform(...);
}
}
@ExtendWith
这篇教程的代码样例应用了 @ExtendWith 申明来通知 JUnit 5 来开启 Spring 反对。在 Spring Boot 2.1,咱们不再须要加载 SpringExtension 了,因为它曾经被蕴含在像 @DataJpaTest,@WebMvcTest 和 @SpringBootTest 这样的 Spring Boot 测试申明中了。
当初咱们能够在所有咱们在利用上下文中须要的 bean 上应用 @Autowire 了。Spring Boot 会主动提供像 @ObjectMapping 这样的 bean 来做映射并从 JSON 和 MockMvc 实例来模仿 HTTP 申请。
咱们应用 @MockBean 来模仿业务逻辑,因为咱们并不想测试 controller 与业务逻辑的集成,而只是要测试 controller 与 Http 层的集成。@MockBean 主动用 Mockito 的 mock 来替换利用上下文与被替换的 bean 同类型的 bean。
你能够在我讲述模仿的文章来看更多对于 @MockBean 的内容。
应用 @WebMvcTest
在上例中通过将 controller 的参数设置到 RegisterRestController.class 上,咱们通知 Spring Boot 在创立上下文时限度给定的 controller 和一些 Spring Web MVC 框架的 bean。而其余咱们可能须要的 bean 被 @MockBean 隔离或模仿掉了。如果咱们不传 controllers 参数,Spring Boot 会加载利用上下文中的所有 controller。这样咱们就须要加载或模仿每个 controller 依赖的所有 bean。这回事测试的配置变得复杂的多,但因为所有的 controller 测试都能够重用雷同的利用高低线而节俭了工夫。我打算将利用上下文放大来限度 controller 测试,这样能够让测试放弃独立,不须要引入其余的 bean,只管这样会让 Spring Boot 在每次单个测试时都会建一个新的利用上下文。让咱们看一下每个职责,并看看如果通过应用 MockMvc 来校验每项职责来进行最佳的集成测试。
插入一条举荐内容,我与其余 2 位作者一起翻译的 Spring 5 设计模式 21 年 2 月曾经在京东等电商渠道上架了,本书次要探讨了在 Spring 框架中应用的经典设计模式,能帮忙开发者理解 Spring 的外部原理,是一本不错的学习书籍。
本文来自祝坤荣 (时序) 的微信公众号「麦芽面包,id「darkjune_think」转载请注明。
开发者 / 科幻爱好者 / 硬核主机玩家 / 业余翻译
微博: 祝坤荣
B 站: https://space.bilibili.com/23…
交换 Email: zhukunrong@yeah.net