原文 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:
@RequiredArgsConstructorclass 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