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的支持(PowerMockPowerMock 1.7.0起增加了支持。PowerMockGitHub上的文档建议Mockito 2.1.0之后的版本使用Mockito)。
4. 踩坑记录
4.1 Mockito未返回预期值,实际执行返回null

注意参数是否匹配,若是精确匹配,注意是equals比较(引用类型对象equals方法是否重写)。
若出现NullPointerException,必然是null.method() 。
一个午觉引发的写案之 Mockito.when返回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示例:四个文件
MockitoTest .java、User.java、PageParam.java、org.mockito.plugins.MockMaker。
MockitoTest .java

//注意1:@Test的测试方法必须是public的,否则:java.lang.Exception: Method XXX should be public
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {

    @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示例:UserServiceTest.java

@RunWith(PowerMockRunner.class)
@PrepareForTest(User.class) //Static.class 是包含 static methods的类
public class UserServiceTest {
		…………………………
@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));
    }
    		…………………………
参考资料