Mockito PowerMock 的demo示例及踩坑記錄

1. Mockito 和 PowerMock是做什麼的

  • Mockito:單元測試框架,通過模擬進行單元測試。
  • owerMock:Mockito和EasyMock的功能擴展框架。

2. Mockito 和 PowerMock的實現原理是什麼

  • Mockito:通過代理(bytebuddy動態生成匿名子類)實現類功能的模擬。
  • PowerMock:通過修改字節碼實現類功能的模擬。

3. Mockito 和 PowerMock的區別

// mockito最開始使用的Cglib創建動態代理,後來使用bytebuddy。
實現原理的不同,PowerMock可以實現對staticfinal等方法的mock。

// 關於Mockito對static method、final class、final method、private method。 Mockito未支持,可以用PowerMock。
static method:還未支持。
private method:Mockito官方認爲,從測試角度來來說,私有方法時不存在的,所以不關心對它的測試。
final classfinal method:Mockito 2.1.0中增加了mocking final classes/methods的支持(PowerMock自PowerMock 1.7.0起增加了支持。PowerMock的GitHub上的文檔建議Mockito 2.1.0之後的版本使用Mockito)。

4. 踩坑記錄

4.1 Mockito未返回預期值,實際執行返回null

注意參數是否匹配,若是精確匹配,注意是equals比較(引用類型對象equals方法是否重寫)。
若出現NullPointerException,必然是null.method() 。
Mockito未返回預期值,實際執行返回null的問題的排查及解決方案

4.2 Mockito與PowerMock的版本對應關係

注意的api依賴包:

4.3 PowerMock 靜態方法、函數參數的模擬

函數參數(Function<E, R> var)精確匹配問題,沒找到該怎麼匹配。
PowerMock 靜態方法模擬問題排查,結果是函數式參數問題

5. demo示例

5.1 github地址

https://github.com/byrc/mockito_demo

5.2 Mockito的模擬過程
// 1.模擬:模擬類或接口(執行模擬對象的方法會被記錄下來)。 eg:模擬List接口
List mockList = Mockito.mock(List.class);

// 有返回值方法
// 2.攔截:指定方法被攔截後返回的值(方法調用,參數匹配通過後,返回指定內容)。
Mockito.when(list.get(1)).thenReturn(11);
// 3.驗證:實際值是否是預期值。eg:通過斷言判斷
TestCase.assertEquals(11, list.get(1));

// 無返回值方法
// 2.攔截:方法被攔截記錄下來
list.get(1);
// 3.驗證:驗證方法(包括方法的參數)是否被記錄。
Mockito.verify(mockList).add(1);
5.3 maven配置,jar引入
        <!-- mockitojar引入 -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.26.0</version>
            <scope>test</scope>
        </dependency>
        <!-- powermock jar引入 -->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>
5.4 項目結構圖

在這裏插入圖片描述

5.5 文件內容

Mockito示例:四個文件
UserServiceTest.java、User.java、PageParam.java、org.mockito.plugins.MockMaker。
UserServiceTest.java

//注意1:@Test的測試方法必須是public的,否則:java.lang.Exception: Method XXX should be public
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    @Before
    public void setup() {
        //這句話執行以後,aaaDao和bbbDao自動注入到abcService中。
        MockitoAnnotations.initMocks(this);
        //在這之後,你就可以放心大膽地使用when().then()、
        //Mockito.doNothing().when(obj).someMethod()、
        //doThrow(new RuntimeException()).when(obj).someMethod(Mockito.any());
        //等進行更詳細的設置。
    }
    @After
    public void  clearMocks() {
        // 避免大量內存泄漏  2.25.0新增
        Mockito.framework().clearInlineMocks();
    }

    @Test // 沒返回值的方法匹配
    public void test0(){
        List mockList = Mockito.mock(List.class);

        mockList.add(1); // 基礎類型
        mockList.add(Lists.newArrayList("a")); // 引用類型

        Mockito.verify(mockList).add(1);
        Mockito.verify(mockList).add(Lists.newArrayList("a"));
    }

    @Test // 有返回值的方法匹配, 參數的三類匹配情況
    public void test1(){
        Map mockMap = Mockito.mock(Map.class);
        // 1.精確匹配
        Mockito.when(mockMap.get(11)).thenReturn(111); // 基礎類型
        Mockito.when(mockMap.get(Lists.newArrayList("袁紫霞"))).thenReturn("白玉京"); // 引用類型
        TestCase.assertEquals(111, mockMap.get(11));
        TestCase.assertEquals("白玉京", mockMap.get(Lists.newArrayList("袁紫霞")));

        // 2.模糊匹配
        Mockito.when(mockMap.get(Mockito.endsWith("天"))).thenReturn("龍傲天"); // 字符串。eg:以天結尾的
        Mockito.when(mockMap.get(Mockito.anyLong())).thenReturn(999L); // 基礎類型. eg:任何long類型
        Mockito.when(mockMap.get(Mockito.any(User.class))).thenReturn(new User()); // 引用類型
        TestCase.assertEquals("龍傲天", mockMap.get("星期天"));
        TestCase.assertEquals(999L, mockMap.get(1L));
        TestCase.assertEquals(new User(), mockMap.get(new User()));

        // 3.自定義匹配。 eg:定義只匹配PageParam的pageNo屬性
        PageParam pageParam = PageParam.create(1, 20);
        ArgumentMatcher<PageParam> argPage = (page) -> page.getPageNo() == pageParam.getPageNo();
        Mockito.when(mockMap.get(Mockito.argThat(argPage))).thenReturn(Lists.newArrayList("袁紫霞", "白玉京"));
        TestCase.assertEquals(Lists.newArrayList("袁紫霞", "白玉京"), mockMap.get(PageParam.create()));
    }

    @Test // final類型方法或類的匹配
    public void test2(){
        /*
        * 正常情況下,final/private/equals()/hashCode() methods不能被攔截或驗證
        * 支持模擬final class,enum和final method(自2.1.0起)
        * https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable
        * 文件 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 中添加:mock-maker-inline
         */
        User mockUser = BDDMockito.mock(User.class);
        Mockito.when(mockUser.finalMethod()).thenReturn(false);
        TestCase.assertEquals(false, mockUser.finalMethod());

    }

    @Test // 自定義返回結果
    public void test3(){
        Map mockMap = Mockito.mock(Map.class);

        Answer answer = (invocation) -> {
            Object[] args = invocation.getArguments();
            return String.valueOf(args[0]) + 110;
        };
        Mockito.when(mockMap.get(anyInt())).then(answer);
        TestCase.assertEquals("120110", mockMap.get(120));
    }

    @Test // 設置拋出異常
    public void test4(){
        List mockList = Mockito.mock(List.class);

        // 1.無返回值方法 設置異常
        Mockito.doThrow(RuntimeException.class).when(mockList).add(1);
        try {
            mockList.add(1);
        }catch (RuntimeException e){
            System.out.println("doThrow(RuntimeException.class).when(mock).someVoidMethod();");
        }

        // 2.有返回值方法 設置異常
        Mockito.when(mockList.get(1000)).thenThrow(new IndexOutOfBoundsException("數組下標越界"));
        try {
            mockList.get(1000);
        }catch (IndexOutOfBoundsException e){
            System.out.println(e.getMessage());
        }
    }

    @Test //連續的方法調用設置不同的行爲
    public void test5(){
        List mockList = Mockito.mock(List.class);
        Mockito.when(mockList.get(1000)).thenReturn(11,22).thenThrow(new IndexOutOfBoundsException("數組下標越界"));
        TestCase.assertEquals(11, mockList.get(1000)); // 第一次調用返回值:11
        TestCase.assertEquals(22, mockList.get(1000)); // 第二次調用返回值:22
        try {
            mockList.get(1000); // 第三次調用拋出異常
        }catch (IndexOutOfBoundsException e){
            System.out.println(e.getMessage());
        }
    }

    @Test // 測試方法的調用次數
    public void test6(){
        /*
        * times(n):方法被調用n次。
        * never():沒有被調用。
        * atLeast(n):至少被調用n次。
        * atLeastOnce():至少被調用1次,相當於atLeast(1)。
        * atMost():最多被調用n次。
         */
        List mockList = Mockito.mock(List.class);
        mockList.add("one times");
        mockList.add("2 times");
        mockList.add("2 times");
        Mockito.verify(mockList, Mockito.atMost(1)).add("one times");
        Mockito.verify(mockList, Mockito.times(2)).add("2 times");
    }

}

org.mockito.plugins.MockMaker

mock-maker-inline

User.java

@Data
public class User {
	private Long id;
	private String name;
	private Integer age;
	private String idCard;

	public static UserDTO convert(User var){
		UserDTO dto = new UserDTO();
		dto.setName(var.getName());
		dto.setAge(var.getAge());
		return dto;
	}

	public final boolean finalMethod(){
		return true;
	}
}

PageParam.java

@Getter
@Setter
public class PageParam {
	private long pageNo;
	private long pageSize;

	public static PageParam create() {
		return create(1L, 10L);
	}
	public static PageParam create(long pageNo, long pageSize) {
		return new PageParam(pageNo, pageSize);
	}
	public PageParam(long pageNo, long pageSize) {
		if (pageNo < 1L) {
			this.pageNo = 1L;
		}
		this.pageNo = pageNo < 1L ? 1L : pageNo;
		this.pageSize = pageSize <= 0L ? 10L : pageSize;
	}
}

PowerMock示例:PowerMockTest.java

@RunWith(PowerMockRunner.class)
@PrepareForTest(User.class) //Static.class 是包含 static methods的類
public class PowerMockTest {
		…………………………
@Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        // 模擬靜態類(使用PowerMockito.spy(class)模擬特定方法)
		PowerMockito.mockStatic(User.class);
    }
    @Test // 靜態方法匹配
    public void test11(){
        User user = new User();
        user.setName("白玉京");
        UserDTO dto = new UserDTO();
        dto.setName("白玉京");

        //PowerMock 2.0.7 對下面方法的支持 也就到Mockito 2.26.X版本爲止
        Mockito.when(User.convert(user)).thenReturn(dto);
        TestCase.assertEquals(dto, User.convert(user));
    }
    		…………………………

參考資料
Mockito的GitHub:https://github.com/mockito/mockito
PowerMock的GitHub:https://github.com/powermock/powermock
Mockito的文檔:https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html

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