文章目錄
1. Mockito 和 PowerMock是做什麼的
- Mockito:單元測試框架,通過模擬進行單元測試。
- owerMock:Mockito和EasyMock的功能擴展框架。
2. Mockito 和 PowerMock的實現原理是什麼
- Mockito:通過代理(bytebuddy動態生成匿名子類)實現類功能的模擬。
- PowerMock:通過修改字節碼實現類功能的模擬。
3. Mockito 和 PowerMock的區別
// mockito最開始使用的Cglib創建動態代理,後來使用bytebuddy。
實現原理的不同,PowerMock可以實現對static和final等方法的mock。
// 關於Mockito對static method、final class、final method、private method。 Mockito未支持,可以用PowerMock。
static method:還未支持。
private method:Mockito官方認爲,從測試角度來來說,私有方法時不存在的,所以不關心對它的測試。
final class、final 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依賴包:
- powermock-api-mockito 支持 mockito 1.x 。
- powermock-api-mockito2 支持 mockito 2.x 。
Mockito與PowerMock的版本對應問題的排查過程
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