关于springboot:用WebMvcTest测试MVC-Web-Contorller一

2次阅读

共计 4267 个字符,预计需要花费 11 分钟才能阅读完成。

原文 https://reflectoring.io/sprin…
翻译: 祝坤荣

在这个测试 Spring Boot 系列的第二局部,咱们来看下 web contoller。开始,咱们会摸索下 web controller 到底做了什么,而后咱们能够基于写单元测试来笼罩所有它的职责。

而后,咱们来看看如果在测试用笼罩这些职责。只有当所有这些职责都被笼罩到了,咱们才能够必定咱们的 contoller 的行为应该与线上环境一样了。

样例代码

这篇文章提供在 GitHub 上的可运行代码。

测试 Spring Boot 系列

这篇教程是一个系列的一部分:

  1. Spring Boot 的单元测试
  2. 应用 @WebMvcTest 测试 Spring Boot 的 MVC Web Controller
  3. 应用 @DataJpaTest 测试 Spring Boot 的 JPA Queries
  4. 应用 @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

正文完
 0