专栏首页软件测试那些事使用MockMVC进行Controller单元测试

使用MockMVC进行Controller单元测试

引入

由于MockMVC是Spring框架自带的测试组件,因此只要在项目中引入spring-boot-starter-test这个测试套件就可以使用Spring-test库中的MockMVC了。 例如Maven项目的pom.xml中添加如下的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

以下将介绍如何使用MockMVC+Mockito+JUnit5+JsonUnit进行测试

待测Controller接口

package com.testlink4j.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.testlink4j.domain.Keywords;
import com.testlink4j.service.KeywordsService;

/**
 * Keywords RestController
 *
 * @author Antony
 * 
 */
@RestController
public class KeywordsRestController {

    @Autowired
    private KeywordsService keywordsService;
    @RequestMapping(value = "/api/keywords", method = RequestMethod.GET)
    public Keywords findKeywordById(@RequestParam(value = "id", required = true) Integer id) {
        return keywordsService.findKeywordById(id);
    }
    @PostMapping(value = "/api/keywords")
    public Integer createKeywords(@RequestBody Keywords keywords) {
        return keywordsService.createKeywords(keywords);
    }
    
}

KeywordsRestController 包含了两个Keywords相关的接口,提供查询和创建的功能。 接下来,将以查询接口为例,介绍如何对该接口进行单元测试。

package com.testlink4j.controller;

import com.testlink4j.service.KeywordsService;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.alibaba.fastjson.JSON;
import com.testlink4j.domain.Keywords;
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.status;
import java.io.UnsupportedEncodingException;


public class KeywordsControllerTest {

     protected MockMvc mockMvc;

        @Mock
        private KeywordsService keywordsServic;

        @InjectMocks
        KeywordsRestController keywordsRestController;
        @BeforeEach()
        public void setup() {
            MockitoAnnotations.initMocks(this);
            mockMvc = MockMvcBuilders.standaloneSetup(keywordsRestController).build();  //初始化MockMvc对象

        }

  @Test
  public void CreateKeywordsSuccessfullyTest() throws UnsupportedEncodingException, Exception {
      Keywords keywords=Keywords.builder().id(666).keyword("tester").testproject_id(333).notes("tester").build();
      Mockito.when(keywordsServic.findKeywordById(1)).thenReturn(keywords);
            String responseString = mockMvc.perform(
                    get("/api/keywords?id=1")    //请求的url,请求的方法是Post
                            .contentType(MediaType.APPLICATION_JSON)  //数据的格式
            ).andExpect(status().isOk())    //返回的状态是200
                    .andDo(print())         //打印出请求和相应的内容
                    .andReturn().getResponse().getContentAsString();   //将相应的数据转换为字符串
            assertThatJson(responseString).isEqualTo(keywords);
  }
}

findKeywordById接口通过调用KeywordsService来实现具体的查询功能。首先,和普通的基于Mockito单元测试一样,通过@Mock注解来对这个Service进行mock,并通过@InjectMocks注解实现注入。

接下来,通过

mockMvc = MockMvcBuilders.standaloneSetup(keywordsRestController).build()

将keywordsRestController加载进Spring容器。 在测试执行阶段,通过对URI的访问,查询id=1的keyword。 从测试结果来看,发生了如下的一系列过程 1)Spring容器收到访问请求,并由DispatcherServlet 根据@RequestMapping将请求转发给对应的controller的接口。 2)接口收到请求,通过解析@RequestParam获取入参,并调用对应的方法执行(调用service的测试桩来返回mock结果) 3)返回接口调用结果,即HttpServletResponse 4)对response的状态进行断言(200),并打印请求和响应 5)对响应结果进行断言(json)

来看一下用例执行过程中,通过print()方法打印的请求和响应

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /api/keywords
       Parameters = {id=[1]}
          Headers = [Content-Type:"application/json"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.testlink4j.controller.KeywordsRestController
           Method = com.testlink4j.controller.KeywordsRestController#findKeywordById(Integer)
//中间部分省略
MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"id":666,"keyword":"tester","testproject_id":333,"notes":"tester"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

另外,看一下整个容器的启动耗时

19:29:57.689 [main] INFO org.springframework.test.web.servlet.TestDispatcherServlet - Completed initialization in 6 ms

可以看到,由于整个测试过程中只将被测的controller注入到了Spring容器中,容器的启动过程是非常快速的。

与直接通过类和方法调用的单元测试方式相比,通过使用MockMvc,有如下的不同 1)通过URI进行接口调用,也就是额外测试了DispatcherServlet 和@RequestMapping

2) 对@RequestParam进行了测试(感兴趣的读者可以尝试调用接口时不提供id=1的入参)

3)对接口返回进行了断言

4)对接口返回对象的反序列化进行了断言

下一篇将介绍如何使用MockMvc进行集成测试,并分析MockMVC的具体组成和使用方式。

本文分享自微信公众号 - 软件测试那些事(antony-not-available),作者:风月同天测试人

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-11-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MeterSphere单元测试-Mockito-Inline出场

    在之前的测试旅程中,我们新建了测试计划并将测试用例纳入该计划来执行。以下是上述用例执行之后对添加测试计划的一个代码覆盖率。

    Antony
  • 看,Mockito如何搞定Builder模式的Fluent API

    建造者模式Builder是一种常用的设计模式,用于构建不同的产品类。 如有以下的Builder

    Antony
  • 2021第一篇-流量录制回放完整案例

    在之前的《录制回放实现测试用例自由》一文中,笔者简单介绍了如何通过切面来录制HTTP接口请求和返回,并实现了用例的回放。 当然,在实际的项目中,对于应用来说,除...

    Antony
  • SpringCloud gateway全局异常处理,以及后台的服务异常response的异常包装

    gateway自己服务的全局异常处理,参考这篇https://segmentfault.com/a/1190000016854364?utm_source=ta...

    天涯泪小武
  • Spring Controller单元测试

    SpringMVC controller测试较简单,从功能角度划分,可分为两种。一种是调用请求路径测试,另一种是直接调用Controller方法测试。 调用请求...

    YGingko
  • 使用JUnit4测试Spring

    注意被测试对象在Spring中不能配置AOP切面代理,否则注入到TestCase时,会产生类型不匹配的异常。因为被代理后的类型发生了变化,注入到TestCase...

    WindWant
  • 单元测试(Spring)

    单元测试是指对软件中的最小可测试单元进行的检查和验证,是软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。 单...

    YGingko
  • springboot2.0 添加全局异常拦截,防止详细的异常信息返回到客户端

    zcqshine
  • springmvc 文件下载 VS resteasy 文件上传下载

    浏览器输入: http://localhost:8080/evaluate/downloadPathExportTemplate

    MickyInvQ
  • SpringBoot开发案例之整合mail队列进阶篇

    上一篇文章,我们为了解决实际场景中遇到的问题,使得其更像一个安全高效的邮件服务平台,我们引入了LinkedBlockingQueue队列对邮件发送进行流量削锋、...

    小柒2012

扫码关注云+社区

领取腾讯云代金券