單元測試利器 - Mock

 

爲什麼會有這篇blog呢,起因上週末看了一個朋友的代碼。

結果隨便打開他們的測試用例,看一下,這是嘛?

週末補了一下這塊的空缺,學無止盡嘛:大致看了一下主體還是很簡單的,

 

官網地址:https://site.mockito.org/

GitHub地址:https://github.com/hehonghui/mockito-doc-zh#0

文檔地址:https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html

 

 

目錄

springboot快速搭建一個web項目,用作編寫測試用例代碼

測試用例 - mockmvc

測試用例 - Mockito


springboot快速搭建一個web項目,用作編寫測試用例代碼

 

pom.xml(基於boot2.x,已經內置junit5跟mock了)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

 

花幾分鐘寫一個添加用戶的功能,DB操作就直接寫僞sql了,爲啥?因爲我懶...

 

controller

package com.mock.demo.controller;

import com.mock.demo.entry.User;
import com.mock.demo.service.SmsService;
import com.mock.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import static com.mock.demo.service.impl.UserServiceImpl.userMap;

/**
 * @author 954L
 * @create 2020/3/24 20:44
 */
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private SmsService smsService;

    @PostMapping("user")
    public Map<String, Object> addAndSendSms(@RequestBody User user,
                                             @RequestParam(value = "sendSmsType", defaultValue = "0")
                                                     Integer sendSmsType) {
        Map<String, Object> result = new HashMap<>();
        String name = user.getName();
        // 發送註冊短信
        boolean status = sendSmsType.equals(1) ? smsService.sendSms() : true;
        // 查重 & 添加
        if (status) status = userMap.get(name) != null ?
                false : userService.add(user);
        result.put("status", status);
        return result;
    }

    @DeleteMapping("user/{name}")
    public Map<String, Object> del(@PathVariable("name") String name){
        Map<String, Object> result = new HashMap<>();
        // 先添加,再刪除
        userService.add(User.builder().name(name).build());
        boolean status = userService.del(name) ;
        result.put("status", status);
        return result;
    }

}

 

service

package com.mock.demo.service;

import com.mock.demo.entry.User;

/**
 * @author 954L
 * @create 2020/3/24 20:47
 */
public interface UserService {

    boolean add(User user);

    boolean del(String name);

}
package com.mock.demo.service;

/**
 * @author 954L
 * @create 2020/3/24 20:39
 */
public interface SmsService {

    boolean sendSms();

}

 

impl

package com.mock.demo.service.impl;

import com.mock.demo.entry.User;
import com.mock.demo.service.UserService;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 954L
 * @create 2020/3/24 20:47
 */
@Service
public class UserServiceImpl implements UserService {

    public static Map<String, User> userMap = new ConcurrentHashMap();

    @Override
    public boolean add(User user) {
        // 添加用戶
        userMap.put(user.getName(), user);
        return userMap.containsKey(user.getName());
    }

    @Override
    public boolean del(String name) {
        userMap.remove(name);
        return !userMap.containsKey(name);
    }

}
package com.mock.demo.service.impl;

import com.mock.demo.service.SmsService;
import org.springframework.stereotype.Service;

/**
 * @author 954L
 * @create 2020/3/24 20:39
 */
@Service
public class SmsServiceImpl implements SmsService {

    /**
     * 提供給mockito測試模擬對象,返回true
     * @return
     */
    public boolean sendSms(){
        // 無短信功能
        return false;
    }

}

 

entity(代碼包名寫錯了,將就下)

package com.mock.demo.entry;

import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;

/**
 * @author 954L
 * @create 2020/3/24 20:46
 */
@Data
@Builder
public class User {

    private String name;
    private Integer age;

    @Tolerate
    public User(){}

}

 

測試用例 - mockmvc

mockmvc

模擬web程序發送一個請求

方法 說明
mockMvc.perform 執行一個請求
MockMvcRequestBuilders.get("XXX") 構造一個請求
ResultActions.param 添加請求傳值
ResultActions.accept()) 執行一個請求 如MediaType.TEXT_HTML_VALUE
ResultActions.andExpect 添加執行完成後的斷言。 等同於 Assert.assertEquals
ResultActions.andDo 添加一個結果處理器,表示要對結果做點什麼事情,比如此處使用MockMvcResultHandlers.print()輸出整個響應結果信息
ResultActions.andReturn 表示執行完成後返回相應的結果。

controllerMockMvcTest

package com.mock.demo.controller;

import com.mock.demo.MockApplication;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

/**
 * @author 954L
 * @create 2020/3/24 20:03
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MockApplication.class)
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
class UserControllerMockMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void add() throws Exception {
        JSONObject paramJson = new JSONObject();
        paramJson.put("name", "Tony");
        paramJson.put("age", "18");
        mockMvc.perform(MockMvcRequestBuilders.post("/user")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(paramJson.toString())
                .accept(MediaType.ALL))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    void del() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.delete("/user/Tony")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.ALL))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}

 

測試用例 - Mockito

mockito

模擬外部依賴,將模擬產生的外部依賴數據與測試代碼一起執行

方法 說明
Mockito.mock(classToMock) 模擬對象
Mockito.verify(mock) 驗證行爲是否發生
Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) 觸發時第一次返回value1,第n次都返回value2
Mockito.doThrow(toBeThrown).when(mock).[method] 模擬拋出異常。
Mockito.mock(classToMock,defaultAnswer) 使用默認Answer模擬對象
Mockito.when(methodCall).thenReturn(value) 參數匹配
Mockito.doReturn(toBeReturned).when(mock).[method]

參數匹配(直接執行不判斷)

Mockito.when(methodCall).thenAnswer(answer)) 預期回調接口生成期望值
Mockito.doAnswer(answer).when(methodCall).[method] 預期回調接口生成期望值(直接執行不判斷)
Mockito.spy(Object) 用spy監控真實對象,設置真實對象行爲
Mockito.doNothing().when(mock).[method] 不做任何返回

Mockito.doCallRealMethod().when(mock).[method] //等價於Mockito.when(mock.[method]).thenCallRealMethod();

調用真實的方法
reset(mock) 重置mock

 

controllerMockItoTest

package com.mock.demo.controller;

import com.mock.demo.MockApplication;
import com.mock.demo.entry.User;
import com.mock.demo.service.SmsService;
import com.mock.demo.service.UserService;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;

import static com.mock.demo.service.impl.UserServiceImpl.userMap;

/**
 * @author 954L
 * @create 2020/3/24 20:03
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MockApplication.class)
@ExtendWith(SpringExtension.class)
class UserControllerMockItoTest {

    @Autowired
    private UserService userService;

    @Test
    void add() {
        // 測試添加用戶service層,因爲依賴短信功能,故這裏模擬短信service功能
        SmsService smsService = Mockito.mock(SmsService.class);
        // 當執行smsService.sendSms(),模擬返回true
        Mockito.when(smsService.sendSms()).thenReturn(true);

        User user = User.builder().name("testName").age(20).build();
        String name = user.getName();
        // 發送註冊短信
        boolean status = smsService.sendSms();
        // 查重 & 添加
        if (status) status = userMap.get(name) != null ?
                false : userService.add(user);
        Assert.assertTrue(status);
    }

}

 

 

對你有幫助的話,右上角給個讚唄~

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章