Mockito測試 示例

相關博客:Mockito原理
本博客相關代碼:代碼下載

步驟

整個測試過程非常有規律:

  1. 準備測試環境
  2. 通過MockMvc執行請求
    3.1. 添加驗證斷言
    3.2. 添加結果處理器
    3.3. 得到MvcResult進行自定義斷言/進行下一步的異步請求
  3. 卸載測試環境

spring提供了mockMvc模塊,可以模擬web請求來對controller層進行單元測試

示例:MockMvc

MockMvc
Spring提供了mockMvc模塊,可以模擬web請求來對controller層進行單元測試

第一步:添加Maven依賴

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

第二步:統一返回結果相關

Result.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    // 請求響應狀態碼(200、400、500)
    private int code;
    // 請求結果描述信息
    private String msg;
    // 請求結果數據
    private T data;

    public Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Result(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    }
}

ResultCode.java

public enum ResultCode {
    // 成功
    OK(200, "OK"),
    // 服務器錯誤
    INTERNAL_SERVER_ERROR(500, "Internal Server Error");

    // 操作代碼
    int code;
    // 提示信息
    String msg;

    ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

ResultUtil.java

public class ResultUtil {
    /**
     * 操作成功,返回具體的數據、結果碼和提示信息
     * 用於數據查詢接口
     * @param data
     * @return
     */
    public static Result success(Object data) {
        Result<Object> result = new Result(ResultCode.OK);
        result.setData(data);
        return result;
    }
    /**
     * 操作失敗,只返回結果碼和提示信息
     *
     * @param resultCode
     * @return
     */
    public static Result fail(ResultCode resultCode) {
        return new Result(resultCode);
    }
}

第三步:實體類

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class Dept {
    private Integer deptno;
    private String dname;
    private String loc;
}

第四步:數據庫模擬類

public class DeptTable {
    private static ArrayList<Dept> depts = new ArrayList<>();
    static {
        depts.add(new Dept(10, "ACCOUNTING", "CHICAGO"));
        depts.add(new Dept(20, "RESEARCH", "DALLAS"));
        depts.add(new Dept(30, "SALES", "CHICAGO"));
        depts.add(new Dept(40, "OPERATIONS", "BOSTON"));
    }
    public static boolean insert(Dept dept) {
        boolean res = depts.add(dept);
        return res;
    }
    public static boolean update(Dept dept) {
        Integer deptno = dept.getDeptno();
        boolean flag = false;
        for (int i = 0; i < depts.size(); i++) {
            Dept temp = depts.get(i);
            if (temp.getDeptno().equals(deptno)) {
                depts.set(i, dept);
                flag = true;
            }
        }
        return flag;
    }
    public static boolean delete(Integer deptno) {
        boolean flag = false;
        for (int i = 0; i < depts.size(); i++) {
            Dept dept = depts.get(i);
            if (dept.getDeptno().equals(deptno)) {
                depts.remove(i);
                flag = true;
            }
        }
        return flag;
    }
    public static Dept selectByDeptno(Integer deptno) {
        for (int i = 0; i < depts.size(); i++) {
            Dept dept = depts.get(i);
            if (dept.getDeptno().equals(deptno)) {
                return dept;
            }
        }
        return null;
    }
    public static List<Dept> selectAll() {
        return depts;
    }
    public static void output() {
        for (Dept dept : depts) {
            System.out.println(dept);
        }
    }
}

第五步:Controller接口一

@Slf4j
@RestController
@RequestMapping("/mock")
public class DeptController {

    //增加Dept ,使用POST方法
    @PostMapping(value = "/dept")
    public Result saveDept(@RequestBody Dept dept) {
        boolean res = DeptTable.insert(dept);
        log.info("saveDept:{}", dept);
        DeptTable.output();
        if (res) {
            return ResultUtil.success(dept);
        } else {
            return ResultUtil.fail(ResultCode.INTERNAL_SERVER_ERROR);
        }
    }

    @GetMapping("/allDept")
    public Result getUserList() {
        List<Dept> depts = DeptTable.selectAll();
        return ResultUtil.success(depts);
    }

    @GetMapping("allDept2")
    public List<Dept> allDept2() {
        List<Dept> depts = DeptTable.selectAll();
        return depts;
    }

    @GetMapping("dept/{deptno}")
    public Dept selectByDeptno(@PathVariable int deptno) {
        Dept dept = DeptTable.selectByDeptno(deptno);
        return dept;
    }

    @DeleteMapping("dept/{deptno}")
    public Result deleteByDeptno(@PathVariable int deptno) {
        boolean res = DeptTable.delete(deptno);
        DeptTable.output();
        if (res) {
            return ResultUtil.success(deptno);
        } else {
            return ResultUtil.fail(ResultCode.INTERNAL_SERVER_ERROR);
        }
    }

    //////////////////////////////////////////////////////////////////////////////

    @GetMapping("fun1")
    public String fun1(){
        return "hello";
    }

    @GetMapping("dept2")
    public Dept selectByDeptno2(int deptno) {
        Dept dept =  DeptTable.selectByDeptno(deptno);
        return dept;
    }

    @PostMapping("fun4")
    public Dept fun4(Dept dept) {
        dept.setDeptno(8989);
        return dept;
    }

    @PutMapping("fun5")
    public Dept fun5(Dept dept) {
        dept.setDeptno(8989);
        return dept;
    }

    @PatchMapping("fun6")
    public Dept fun6(Dept dept) {
        dept.setDeptno(8989);
        return dept;
    }


}

第六步:容器環境下Mockito測試(★★★★)

Servlet容器注入時還可以讓Spring容器注入其它的對象,如果測試方法中還依賴有其它的對象,比如依賴Service,就需要使用這種方式了。

實現一

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class DeptController0Test { //Servlet容器環境下測試
    @Autowired
    private WebApplicationContext wac;

    //mock對象:用來模擬網絡請求
    private MockMvc mockMvc;

    @Before
    public void setup() {
        //mock對象初始化,指定WebApplicationContext,將會從該上下文獲取相應的控制器並得到相應的MockMvc
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    //測試方法:運行測試方法時不需要啓動服務
    @Test
    public void saveDept() throws Exception {
        System.out.println(mockMvc);
        Dept dept = new Dept(10, "ACCOUNTING", "CHICAGO");
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(dept);

        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.request(HttpMethod.POST, "/mock/dept")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json);
        MvcResult result = mockMvc.perform(builder)//執行一個RequestBuilder請求,會自動執行SpringMVC的流程並映射到相應的控制器執行處理
                .andExpect(MockMvcResultMatchers.status().isOk()) //添加RequestMatcher驗證規則,驗證控制器執行完成後是否正確
                .andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
                .andExpect(MockMvcResultMatchers.jsonPath("$.msg").value("OK"))
                .andExpect(MockMvcResultMatchers.jsonPath("$.data.dname").value("ACCOUNTING"))
                .andDo(print()) //添加ResultHandler結果處理器,比如調試時打印結果到控制檯
                .andReturn();//最後返回相應的MvcResult,然後進行自定義驗證/進行下一步的異步處理

        String res = result.getResponse().getContentAsString();
        log.info(res);
    }

    @Test
    public void allDept() throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/mock/allDept")
                .contentType(MediaType.APPLICATION_JSON);
        MvcResult result = mockMvc.perform(builder)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.data[0].dname").value("ACCOUNTING"))
                .andDo(print())
                .andReturn();

        String res = result.getResponse().getContentAsString();
        log.info(res);
    }

    @Test
    public void allDept2() throws Exception {
        MockHttpServletRequestBuilder builder = get("/mock/allDept2")
                .contentType(MediaType.APPLICATION_JSON);
        String result = mockMvc.perform(builder)//構造一個請求
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(4))
                .andReturn()
                .getResponse()
                .getContentAsString();
        System.out.println("Result === " + result);
    }


    @Test
    public void selectByDeptno() throws Exception {
        mockMvc.perform(get("/mock/dept/10"))
                .andExpect(status().isOk())
                .andDo(print());
    }

    @Test
    public void deleteByDeptno() throws Exception {//delete
        mockMvc.perform(delete("/mock/dept/10"))
                .andReturn();
    }

}

方式二

Servlet容器環境測試代碼也可以簡寫爲:

@Slf4j
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest
public class DeptController1Test { //Servlet容器環境下測試
    @Autowired
    private WebApplicationContext wac;

    //mock對象:用來模擬網絡請求
    @Resource
    private MockMvc mockMvc;

    @Test
    public void fun1() throws Exception {
        mockMvc.perform((get("/mock/fun1")))
                .andExpect(MockMvcResultMatchers.content().string("hello"))
                .andExpect(status().isOk()) //添加執行完成後的斷言
                .andDo(print());//添加一個結果處理器,此處表示輸出整個響應的結果信息
    }

    @Test
    public void selectByDeptno2() throws Exception {
        mockMvc.perform((get("/mock/dept2")
                .param("deptno", "30"))) //添加傳值請求
                .andExpect(status().isOk()) //添加執行完成後的斷言
                .andDo(print());//添加一個結果處理器,此處表示輸出整個響應的結果信息
    }

    @Test
    public void fun4() throws Exception {//post
        final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("dname", "sales");
        params.add("loc", "china");
        String mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/mock/fun4")
                .params(params))
                .andReturn()
                .getResponse()
                .getContentAsString();
        System.out.println("Result === " + mvcResult);
    }

    @Test
    public void fun5() throws Exception {//put
        final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("dname", "sales");
        params.add("loc", "china");
        String mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/mock/fun5")
                .params(params))
                .andReturn()
                .getResponse()
                .getContentAsString();
        System.out.println("Result === " + mvcResult);
    }

    @Test
    public void fun6() throws Exception {//patch
        final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("dname", "sales");
        params.add("loc", "china");
        String mvcResult = mockMvc.perform(MockMvcRequestBuilders.patch("/mock/fun6")
                .params(params))
                .andReturn()
                .getResponse()
                .getContentAsString();
        System.out.println("Result === " + mvcResult);
    }

}

第七步:Controller接口二

@Slf4j
@Controller
@RequestMapping("/mock2")
public class DeptController2 {

    @GetMapping("/dept/{deptno}")
    public ModelAndView selectByDeptno(@PathVariable("deptno") Integer deptno) {
        Dept dept = DeptTable.selectByDeptno(deptno);
        ModelAndView mav = new ModelAndView();
        mav.setViewName("dept/detail");
        mav.addObject("dept", dept);
        return mav;
    }


    @PostMapping("/dept")
    public String saveDept(Dept dept, RedirectAttributes redirect) {
        boolean res = DeptTable.insert(dept);
        redirect.addFlashAttribute("dept", res);
        return "redirect:/dept/list/";
    }

    // 單文件上傳
    @RequestMapping("/fileUpload")
    @ResponseBody
    public String fileUpload(@RequestParam("fileName") MultipartFile file) {
        if (file.isEmpty()) {
            return "file is empty";
        }
        String fileName = file.getOriginalFilename();
        int size = (int) file.getSize();
        System.out.println(fileName + "-->" + size);
        String path = "E:/test";
        File dest = new File(path + "/" + fileName);
        if (!dest.getParentFile().exists()) { //判斷文件父目錄是否存在
            dest.getParentFile().mkdir();
        }
        try {
            file.transferTo(dest); //保存文件
            return "true";
        } catch (IllegalStateException | IOException e) {
            e.printStackTrace();
            return "false";
        }
    }

}

第八步:Mockito上下文測試(★★★)

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class DeptController2Test {

    //mock對象:用來模擬網絡請求
    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new DeptController2())
                .alwaysDo(print())  //全局配置
                //默認每次執行請求後都做的動作
                .alwaysExpect(MockMvcResultMatchers.status().isOk()) //默認每次執行後進行驗證的斷言
                .build();
    }

    //測試普通控制器
    @Test
    public void selectByDeptno() throws Exception {
        System.out.println(mockMvc);
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/mock2/dept/10"))
                .andExpect(MockMvcResultMatchers.model().attributeExists("dept"))
                .andExpect(MockMvcResultMatchers.forwardedUrl("dept/detail"))
                .andDo(print())
                .andReturn();

        String res = result.getResponse().getContentAsString();
        log.info(res);
    }

    //得到MvcResult自定義驗證
    @Test
    public void selectByDeptno2() throws Exception {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/mock2/dept/{deptno}", 10))//執行請求
                .andReturn(); //返回MvcResult
        Assert.assertNotNull(result.getModelAndView().getModel().get("dept")); //自定義斷言
    }

    //驗證請求參數綁定到模型數據及Flash屬性
    @Test
    public void saveDept() throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/mock2/dept")
                .param("deptno", "50")
                .param("dname", "zhang")
                .param("loc", "san");
        mockMvc.perform(builder) //執行傳遞參數的POST請求(也可以post("/user?name=zhang"))
                .andExpect(MockMvcResultMatchers.handler().handlerType(DeptController2.class)) //驗證執行的控制器類型
                .andExpect(MockMvcResultMatchers.handler().methodName("saveDept")) //驗證執行的控制器方法名
                .andExpect(MockMvcResultMatchers.model().hasNoErrors()) //驗證頁面沒有錯誤
                .andExpect(MockMvcResultMatchers.flash().attributeExists("dept")) //驗證存在flash屬性
                .andExpect(MockMvcResultMatchers.view().name("redirect:/dept/list/"))
                .andDo(print()); //驗證視圖
    }

    //上傳測試
    @Test
    public void fileTest() throws Exception {
        MockMultipartFile mmf = new MockMultipartFile("fileName", "aim.xlsx", "application/ms-excel", new FileInputStream(new File("E:/ABCD.xlsx")));
        MockMultipartHttpServletRequestBuilder buider = MockMvcRequestBuilders.fileUpload("/mock2/fileUpload").
                file(mmf);

        ResultActions resultActions = mockMvc.perform(buider);
        MvcResult mvcResult = resultActions.andDo(MockMvcResultHandlers.print())
                .andReturn();
        String result = mvcResult.getResponse().getContentAsString();
        System.out.println("==========結果爲:==========\n" + result + "\n");
    }

}

輕量級測試:僅僅測試Controller層

  • Service
@Service
public class DeptService {
    public String ff(){
        System.out.println("--------------DeptService ff()----------------");
        return "正確";
    }
}
  • DeptController
@RestController
@RequestMapping("/mock3")
public class DeptController3 {
    @Resource
    private DeptService deptService;

    @GetMapping("/fun1")
    public void fun1() {
        System.out.println("Controller 層執行");
        deptService.ff();
    }
}
  • 測試代碼
@Slf4j
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@WebMvcTest
public class DeptController3Test { //Servlet容器環境下測試
    //mock對象:用來模擬網絡請求
    @Resource
    private MockMvc mockMvc;

    @MockBean //僞造一個Service
    private DeptService deptService;

    @Test
    public void fun1() throws Exception {
        //打樁:設置當調用 deptService.ff()時的返回值爲"ok",而不用返回deptService.ff()方法的真正返回值"正確",而不用真正執行service中的方法
        when(deptService.ff()).thenReturn("ok");
        mockMvc.perform((get("/mock3/fun1")))
                .andExpect(status().isOk()) //添加執行完成後的斷言
                .andDo(print());//添加一個結果處理器,此處表示輸出整個響應的結果信息
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章