mockito与powerMock用法汇总

前言

上次整理了单测的学习笔记,所以这次就对java常见的两个单测工具包(mockito、powerMock)进行学习了解,总结一些常见的用法


mockito

1.mock 对象

Person xiaoHong = new Person();
Person xiaoMing = Mockito.mock(Person.class);

在这里插入图片描述

xiaoMing 被加上了一层动态代理 MockMethodInterceptor

2.mock bean 并注入到测试类

使用 @RunWith(MockitoJUnitRunner.class)

@RunWith(MockitoJUnitRunner.class)
public class MockBeanTest {

    @Mock
    PersonCheckService personCheckService;

    @InjectMocks
    HospitalController hospitalController;

    @Test
    public void mockBeanTest(){
    }
}

使用 MockitoAnnotations.openMocks();

public class MockBeanTest {

    @Mock
    PersonCheckService personCheckService;

    @InjectMocks
    HospitalController hospitalController;

    @Before
    public void init() {
        MockitoAnnotations.openMocks(this);
        // 或 MockitoAnnotations.openMocks(MockBeanTest.class);
    }

    @Test
    public void mockBeanTest(){
    }
}

手动mock(一般来说不用手动mock,需要写构造函数或者set方法)

public class MockBeanTest {

    PersonCheckService personCheckService;

    HospitalController hospitalController;

    @Before
    public void init() {
    	hospitalController = new HospitalController();
    	
        personCheckService = Mockito.mock(PersonCheckService.class);
        hospitalController.setPersonCheckService(personCheckService);
    }

    @Test
    public void mockBeanTest(){
    }
}

3.验证是否执行了某些操作

Mockito.verify 用于验证方法在给定的方法参数下是否被调用

@Test
public void verifyTest(){
    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    hospitalController.check(xiaoMing);
    Mockito.verify(personCheckService).checkPerson(xiaoMing);	// √
    Mockito.verify(personCheckService).checkPerson(xiaoHong);	// ×
}

verify 默认验证的方法只会执行一次,如果将上面用例改成下面这样,就会发生报错

@Test
public void verifyTest(){
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);

    hospitalController.check(xiaoMing);
    hospitalController.check(xiaoMing);

    Mockito.verify(personCheckService).checkPerson(xiaoMing);// ×
}

对于特殊的情况,可以对 verify 进行验证模式的配置

例如将上面用例更改验证模式为 times(2),代表会执行2次

Mockito.verify(personCheckService, times(2)).checkPerson(xiaoMing);// √

相关的验证模式选择还有很多:

验证模式VerificationMode
行为确切发生n次times(n)
行为从不发生never()
行为至少发生一次atLeastOnce()
行为至少发生n次atLeast(n)
行为最多发生n次atMost(n)

除了对 verify 模式进行指定之外,Mockito 提供一些常见的验证行为的方法

  • 验证没有任何交互,即没有调用到 mock 对象的任意一个方法
@Test
public void verifyNoInteractionsTest() {
    // 此处直接 mock 了传入的对象用于测试,实际上应该会对测试类中被 mock 的类对象进行 verifyNoInteractions
    Person xiaoHong = mock(Person.class);
    Person xiaoMing = mock(Person.class);

    hospitalController.check(xiaoMing);
    Mockito.verifyNoInteractions(xiaoHong);     // √
    Mockito.verifyNoInteractions(xiaoHong,xiaoMing);    // ×
}
  • 如果我们需要保证除了我们 verify 的方法调用之外,没有别的冗余的交互时,以上的验证方法就比较难实现了,就需要使用到下面示例
@Test
public void verifyNoMoreInteractionsTest() {
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);

    hospitalController.check(xiaoMing);

    Mockito.verify(personCheckService, times(1)).checkPerson(xiaoMing);     // √
    Mockito.verifyNoMoreInteractions(personCheckService);    // √
    hospitalController.get(xiaoMing);
    Mockito.verifyNoMoreInteractions(personCheckService);    // × 因为上面的操作没有被 verify ,归为冗余操作
}

4.打桩(打桩为测试注入了灵魂)

通过打桩,可以让被我们mock的对象,在调用指定方法的时候,按照我们的需要返回。

这里还是多写一下关于打桩。

打桩的目的主要有:隔离、补齐、控制。

  • 隔离

    单测的时候需要保证我们测试的单元一定要独立,不能依赖于外部的方法或接口,单元中调用到的方法也需要有对应的单测来保证功能。

  • 补齐

    如果我们依赖的外部方法还没开发,或者是项目是并行开发,那此时打桩就可以把未完成的方法给代替掉。

  • 控制

    测试时,我们对外部方法的实现细节不需要去理解,就像个黑盒,但外部方法的返回结果可能跟我们测试结果有直接关系,那此时我们可以通过打桩,控制外部方法按要求返回我们希望的结果。

前两点在我们 mock 对象时已经已经能够解决,例如以下示例,得到结果并不会是真实的返回结果,而是 mock 对象的方法所提供的默认结果,默认值为 null 或是原始/原始包装器值。

@Test
public void stubTest(){
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    Double result = hospitalController.get(xiaoMing);	// 0.0
}

通过打桩,我们就是要实现对mock的类方法自定义返回结果

@Test
public void stubTest(){
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    when(personCheckService.getBMIOfPerson(xiaoMing)).thenReturn(20.0);
    Double result = hospitalController.get(xiaoMing);	// 20.0 
}

以上用例使用到的when().thenReturn()是最常用的打桩方式,除此之外,如果对于 void 方法,我们要进行打桩的话(例如我们需要让方法在传入某些参数时抛出异常,或者我们要获取到方法参数等情况),就需要使用到

@Test
public void stubTest(){
    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    // doNothing().when(personCheckService).checkPerson(xiaoMing); // 不作方法参数捕获的话,可以不用特地对 void 方法打桩
    doThrow(new RuntimeException()).when(personCheckService).checkPerson(xiaoMing); // 当传入参数为 xiaoMing 时会抛出 RuntimeException 异常
    hospitalController.check(xiaoHong); // 不会抛出异常
    hospitalController.check(xiaoMing); // 抛出异常
}

5.参数匹配器

如果只是用到上述打桩方式,一定程度上来说是不够灵活的,如果我们方法传入的参数多了,一个个配置固定的值去实现打桩就不太现实,所以也就有了参数匹配器,来使得测试更加自如。

例如刚刚的例子,我们现在需要的是 只要 personCheckService.checkPerson 被调用到了,就抛出一个 RuntimeException

@Test
public void anyTest(){
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);
    
    doThrow(new RuntimeException()).when(personCheckService).checkPerson(any()); // 此处用到是参数匹配器 any()
    
    // 以下两个调用单独执行均会抛出 RuntimeException
    hospitalController.check(xiaoHong); 
    hospitalController.check(xiaoMing);
}

相关的参数匹配器还有

参数匹配器作用
any()匹配任意类对象,包括 null 与 可变参数
anyObject()匹配任意类对象,包括 null
any(Class type)匹配给定的类T的任意对象
isA(Class type)匹配实现于类T的所有类对象
anyBoolean、anyByte、anyChar、anyString…同理与any(Class type),只不过把类型写好了

6.验证升级 - 执行顺序的验证

@Test
public void verifyOrderTest() {
    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);

    hospitalController.check(xiaoMing);
    hospitalController.check(xiaoHong);

    InOrder inOrder = Mockito.inOrder(personCheckService);

    inOrder.verify(personCheckService).checkPerson(xiaoMing);
    inOrder.verify(personCheckService).checkPerson(xiaoHong);
}

上面的示例中,如果将两个 inOrder.verify 进行调换,因为执行顺序错误,自然也会验证失败。

开头写 verify 的时候,有写到 verify 默认执行次数为 times 为 1,所以如果如下示例,重复执行两次 check(xiaoMing) ,并在 check(xiaoHong) 之后再执行一次 check(xiaoMing)

@Test
public void verifyOrderTest() {
    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);

    hospitalController.check(xiaoMing);
    hospitalController.check(xiaoMing);
    hospitalController.check(xiaoHong);
    hospitalController.check(xiaoMing);

    InOrder inOrder = Mockito.inOrder(personCheckService);

    inOrder.verify(personCheckService, times(2)).checkPerson(xiaoMing); // 此处不能分开写
    inOrder.verify(personCheckService).checkPerson(xiaoHong);
    inOrder.verify(personCheckService).checkPerson(xiaoMing); // 由于是验证顺序,所以不能计算次数到前面的 verify
}

7.断言

这个就并非是 mockito 组件中的,而是 junit。但这里就一并介绍一下

断言的实现就非常简单,其实只不过是帮我们将 if…else… 给做了,主要是使得代码简洁。

@Test
public void AssertTest() {
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);

    when(personCheckService.getBMIOfPerson(any(Person.class))).thenReturn(20.0);

    Double valueXiaoMing = hospitalController.get(xiaoMing);
    Assert.assertEquals("value is error", Double.valueOf(20.0), valueXiaoMing);
}

断言的其他方法(具体方法参数省略,重写的方法有点多)还有

方法说明
assertTrue断言给定的值为 true
assertFalse断言给定的值为 false
fail执行该断言时直接抛出错误
assertEquals/assertNotEquals断言给定的两个值相等/不相等
assertArrayEquals断言给定的两个数组相等
assertNotNull断言给定的值不为 null
assertSame/assertNotSame断言两个对象引用同一对象/没有引用同一对象

8.打桩升级Ⅰ - 连续打桩

像上文写的打桩,打桩后的效果是被打桩的方法固定返回指定值,如果要产生不同的值,那就需要连续打桩了

@Test
public void continueStubTest() {
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);

    when(personCheckService.getBMIOfPerson(any(Person.class))).thenReturn(20.0,21.0);

    Double valueXiaoMing = hospitalController.get(xiaoMing);    // 20.0
    Double valueXiaoHong = hospitalController.get(xiaoHong);    // 21.0
}

当执行次数超过连续打桩的数量时,返回连续打桩的最后一个值。

9.打桩升级Ⅱ - 获取方法传入参数

使用到了 ArgumentCaptor 指定我们要获得的方法参数类型,并在打桩的时候设置在对应位置上,可以看成是设置了 any(Person.class),在打桩功能不变的情况下,ArgumentCaptor 会捕获到对应的参数,如下示例

@Test
public void ArgumentCaptorTest() {
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);

    ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass(Person.class);
    when(personCheckService.getBMIOfPerson(personCaptor.capture())).thenReturn(20.0);

    Double valueXiaoMing = hospitalController.get(xiaoMing);
    Assert.assertEquals("value is error", Double.valueOf(20.0), valueXiaoMing);
    Assert.assertEquals("Argument is error", xiaoMing, personCaptor.getValue());
}

10.mock 静态方法

Mockito 3.4.0 已经可以实现mock静态方法,如下示例

@Test
public void mockStaticTest() {
    try (MockedStatic<CheckUtil> mockCheckUtil = Mockito.mockStatic(CheckUtil.class)) {
        Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
        mockCheckUtil.when(() -> CheckUtil.getBMIOfPerson(xiaoMing)).thenReturn(10.0);

        Double result = personCheckService.getBMIOfPerson(xiaoMing); // 10.0
    }
}

但注意,静态方法应不应该mock,其实这是一个挺有争议性的话题,可以看 why-doesnt-mockito-mock-static-methods

到了这一步,已经可以满足日常开发中单测编写的需要了,当然还有挺多点没有细写出来。


powerMock

相比于 mockito,powerMock 在兼容 mockito 的基础上,增加了许多特殊的 mock 操作

也就是上面 mockito 的示例,@RunWith(PowerMockRunner.class) 也可以实现测试,此处就不再展开。

1.powerMock 注解

使用 powerMock 最常用的就是以下4个注解,标注在测试类上

  • @RunWith(PowerMockRunner.class)

    表明使用 PowerMockRunner 进行测试

  • @PrepareForTest({})

    可以简单理解成,把需要用到 powerMock 进行特殊 mock 操作的类放到该注解中,这些特殊操作会涉及到字节码级别的修改,所以需要提前准备

    This includes final classes, classes with final, private, static or native methods that should be mocked and also classes that should be return a mock object upon instantiation.
    
  • @SuppressStaticInitializationFor("")

    可阻止对应类的静态代码块执行

  • @PowerMockIgnore()

    表明不使用 powerMock 来加载所声明的 package 路径的类

    如果在使用 powerMock 时出现系统类异常,可以尝试将其对应的包加到该注解中

2.mock 静态方法

现在网上搜索 mock 静态方法,十有八九都是清一色推荐 powerMock 的方法,主要是 Mockito 3.4.0 之前确实不支持,其次也是照搬文化盛行。

如果只是单纯需要 mock 静态方法的话,建议还是只使用 mockito 即可

以下是使用 powerMock 的示例

@Test
public void mockStaticTest() {
    mockStatic(CheckUtil.class);
    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0);
    // when(CheckUtil.getBMIOfPerson(xiaoMing)).thenReturn(10.0); 
    when(CheckUtil.getBMIOfPerson(xiaoMing)).thenAnswer((Answer<Double>) invocation -> 10.0);

    Double result = personCheckService.getBMIOfPerson(xiaoMing); // 10.0
}

至于为什么 when…thenReturn… 会报错以及对应的解决方法,暂时我也没有找到,所以这里用 thenAnswer 代替

3.mock 构造函数

@Test
public void mockNewTest() throws Exception {

    Person xiaoHong = new Person("XiaoHong", 20, 160.0, 50.0);
    PowerMockito.whenNew(Person.class).withAnyArguments().thenReturn(xiaoHong);

    Person xiaoMing = new Person("XiaoMing", 20, 180.0, 70.0); // 会变成 xiaoHong 的数据
}

4.mock final 方法

personCheckService 是被 @Mock 标注的,且需要设置 @PrepareForTest({PersonCheckServiceImpl.class})

其他的跟 mock 其他方法一样

@Test
public void mockFinalTest(){
    when(personCheckService.getPersonInfo()).thenReturn("ok");
    Assert.assertEquals("ok",personCheckService.getPersonInfo());
}

5.mock 私有方法、final 类等等

用法就不做详细说明,大体使用方法一致

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值