Mockito和PowerMock使用总结

本文深入探讨了使用Mockito和PowerMock进行单元测试的方法,包括mock私有方法、静态方法及解决常见问题的策略。通过示例代码,详细讲解了如何避免NPE,处理参数类型,以及解决链式调用带来的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Mockito和PowerMock

编写单元测试时,最常用就是利用mockito、PowerMock进行mock对象。
可以帮我们mock private方法、static方法、静态类等。
一个常见的单元测试如下:这里也mock了static方法。

被测试代码:

@Component
public class SelfService {

    @Autowired
    private SelfCommand selfCommand;

    @Autowired
    private SelfNotify selfNotify;

    public boolean checkParam(Integer param) {
        String message = MessageConvert.convert(param);
        selfNotify.notify(message);
        return check(param);
    }

    private boolean check(int key) {
        return selfCommand.check(key);
    }
}

至于使用到的两个类,尚未实现。我们可以mock他们。

@Component
public class SelfNotify {

    public void notify(String content) {
        // TODO
    }
}
@Component
public class SelfCommand {

    public boolean check(int key) {
        return false;
    }
}

测试代码如下:

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.security.*"}) //忽略一些mock异常
@PrepareForTest({MessageConvert.class})// 因为mock 静态方法,要prepare
public class SelfServiceTest {

    // 把被模拟对象注入.因为PowerMock会自动生成对象,因此这里不能SelfService 不能是接口
    @InjectMocks
    private SelfService selfService;

    @Mock
    private SelfCommand selfCommand;
    // mock 被测试类依赖的属性对象

    @Mock
    private SelfNotify selfNotify;

    @Before
    public void setup() {
        // 因为是void 方法只能用 doNothing()
        // 注意这里的when括号里面只有对象,方法调用写在括号外边
        PowerMockito.doNothing().when(selfNotify).notify(ArgumentMatchers.anyString());

        // mock 静态方法
        PowerMockito.mockStatic(MessageConvert.class);
    }

    @Test
    public void should_get_true_when_pass_check() {
        when(selfCommand.check(ArgumentMatchers.anyInt())).thenReturn(true);

        when(MessageConvert.convert(ArgumentMatchers.anyInt())).thenReturn("mockStatic");

        boolean checkResult = selfService.checkParam(2);

        assertThat(checkResult).isTrue();
        // 验证被Mock的对象的方法是不是真的被调用了,verify 第二个参数默认为被调用了一次
        Mockito.verify(selfCommand).check(ArgumentMatchers.eq(2));
        Mockito.verify(selfNotify).notify(ArgumentMatchers.eq("mockStatic"));
    }

    // 测试私有方法
    @Test
    public void should_can_test_private_method() throws Exception {
        when(selfCommand.check(ArgumentMatchers.anyInt())).thenReturn(true);

        boolean checkResult = Whitebox.invokeMethod(selfService, "check", 1);

        assertThat(checkResult).isTrue();
    }
}

这里测试了私有代码,也mock了静态方法。确实很好用,不过使用的时候也碰到写问题。

问题一:无法直接mock私有方法

类中 public方法A调用 private方法B。即 A =>B 时,无法直接mock整个方法 B。需要写一大堆mock控制B的返回。

    // 无法mock private 方法
    @Test
    public void should_mock_private_for_test_class() throws Exception {
        // 想mock被测试类的某个private 方法,不起作用
        PowerMockito.when(selfService, "check", ArgumentMatchers.any()).thenReturn(true);

        boolean checkResult = selfService.checkParam(2);

        assertThat(checkResult).isTrue();
    }

此处我更想做的是直接mock 方法B的返回。

问题二:注意基本参数类型,防止NPE

    // 注意基本类型. 这里会失败
    @Test
    public void should_get_true_when_pass_check_1() {
        // 这里因为是基本类型,用any()时可能会 NullPointerException
        when(selfCommand.check(ArgumentMatchers.anyInt())).thenReturn(true);

        boolean checkResult = selfService.checkParam(null);

        assertThat(checkResult).isTrue();
    }

这里 SelfCommand.check(int key)的参数是基本类型,会导致对 null拆箱,报NPE。不过这个是java本身的问题,与Mockito无关。

问题三:测试类的继承,避免属性重复

我们通常会把一些通用的方法放在基类里面,这样在写每个测试类时,可以少做一些东西。

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.security.*"}) //忽略一些mock异常
public class BaseTest {

    @Mock
    protected CacheUtil cacheUtil;

    @Before
    public void init() {
        when(cacheUtil.get(ArgumentMatchers.any())).thenReturn("Unit Test");
    }
}

具体测试类:

public class SelfServiceTest extends BaseTest {
    // 把被模拟对象注入.因为PowerMock会自动生成对象,因此这里不能SelfService 不能是接口
    @InjectMocks
    private SelfService selfService;

    @Mock
    private SelfCommand selfCommand;
    // mock 被测试类依赖的属性对象


    // 这里因为父类也 mock 了 该类,会导致使用时空指针
//    @Mock
//    private CacheUtil cacheUtil;

    @Test
    public void should_get_cache_value() {
        when(cacheUtil.get(ArgumentMatchers.any())).thenReturn("Test");

        String value = selfService.getCacheValue("key");

        assertThat(value).isEqualToIgnoringCase("Test");
    }
}

这里测试类中如果也加了与父类相同的属性时,会导致NPE。

问题四:先后两次使用相同参数调用同一个方法

该问题可能因为代码习惯不好。Mockito可以让我们根据不同参数mock不同的返回值:

    @Test
    public void should_get_different_for_differ_key() {
        when(cacheUtil.get(ArgumentMatchers.eq("key1"))).thenReturn("Unit");
        when(cacheUtil.get(ArgumentMatchers.eq("key2"))).thenReturn("Test");

        String value1 = selfService.getCacheValue("key1");
        String value2 = selfService.getCacheValue("key2");

        assertThat(value1 + value2).isEqualToIgnoringCase("UnitTest");
    }

这里根据参数不同mock不同的返回。但是,两次参数一样呢?

@Component
public class SelfService {

    @Autowired
    private SelfCommand selfCommand;

    @Autowired
    private SelfNotify selfNotify;

    @Autowired
    private CacheUtil cacheUtil;

    // ...其他方法

    public String calculateValue(String key) {
        String cache = cacheUtil.get(key);// 1 
        if (StringUtils.isNotEmpty(cache)) {
            return cache;
        }
        String value = selfCommand.get(key);
        cacheUtil.set(key, value);
        return cacheUtil.get(key);// 2
    }
}

这里代码可能写的不好。对于1和2处参数一样,不过缺需要不同的返回值:需要1处返回空字符串,2处返回真正的数据。

尝试了下,可以这样写

when(myService.queryById(ArgumentMatchers.anyLong())).thenReturn(null).thenThrow(Exception.class);

第一调用返回null,第二次抛出异常。

问题五:修改入参对象属性

如下,CoefficientFactor对象作为参数传递给SelfCommand的calculate()方法,在该方法里面对其进行了修改。此时mock无法做到

@Component
public class SelfService {

    @Autowired
    private SelfCommand selfCommand;

    // ...省略

    public Long calculate(Long userId, CoefficientFactor factor) {
        // 当selfCommand.calculate 里面有对 factor进行set 设置值时。此时mock无法模拟
        Long result = selfCommand.calculate(userId, factor);
        // ... 此处代码可能使用factor的属性
        return result * 4;
    }
}

问题六:链式调用

看下这个例子,利用框架进行了流式校验,代码很简洁,但是mock时有点难了。

@Component
public class CombineValidator {

    public MyValidatorResult validate(MyDto mydto, String orderSn) {

        ComplexResult commonResult = FluentValidator.checkAll().failOver()
                .putAttribute2Context("orderSn", orderSn)
                .on(mydto, commonValidator)
                .doValidate(commonValidatorCallback).result(toComplex());

        ComplexResult ret = FluentValidator.checkAll().failOver()
                .putAttribute2Context("orderSn", orderSn)
                .on(orderSn, orderValidator)
                .on(mydto.getUid(), riskDelayValidator)
                .on(mydto.getOperationType, operatorValidator)
                .doValidate().result(toComplex());
        if (commonResult.isSuccess()) {
            // xxx
        }

        if (ret.isSuccess()) {
            // xxx
        }
    }
}

此时因为链式调用并导致了一些封装,对result方法mock起来有些麻烦。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值