PowerMock(一):PowerMock的使用

2021-08-18 11:20:00 浏览数 (1)

您好,我是码农飞哥,感谢您阅读本文!本文主要介绍PowerMock的基本使用。

  • 环境
  • 引入依赖
  • 注解说明
  • mock普通方法
  • mock抛出异常
  • mock新建对象
  • mock无返回值的方法
  • mock被final修饰的方法
  • 参数模糊匹配
  • mock静态方法
  • mock私有方法
  • 总结
  • 参考

为啥要使用PowerMock

现在流行的测试驱动开发TDD(Test-Driven Development) ,是敏捷开发中一项核心实践和技术。也是一种设计方法论。其中最重要的一环就是使用单元测试。单元测试是保证代码质量的一个重要手段,通过单元测试我们可以快速的测试代码的各个分支,各种场景,代码重构时只需要重新跑下单元测试就是能知道代码潜在的问题。单元测试是通过Mock的方式调用被测试的方法,其有如下几个优点:

  1. Mock可以解除测试对象对外部服务的依赖(比如数据库,第三方接口等),使得测试用例可以独立运行。不管是单体应用还是微服务,这点都特别重要。
  2. Mock的第二个好处就是替换外部服务调用,提升测试用例的运行速度。因为任何外部服务调用至少是跨进程级别的消耗,甚至是跨系统、跨网络的消耗,而Mock可以把消耗降低到进程内。
  3. Mock的第三个好处就是提升测试效率,提高单位时间内测试的接口数量。Mock的框架有很多中比如EasyMock等,这里选用PowerMock是因为PowerMock可以用来Mock 私有方法,静态方法以及final方法。EasyMock等则不能。

PowerMock的使用

环境

软件

版本

junit

4.13

powermock

2.0.7

引入依赖

代码语言:javascript复制
  <properties>
        <powermock.version>2.0.7</powermock.version>
    </properties>
  <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <!--powermock开始-->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <!--powermock结束-->

这里引入了是三个依赖,junit依赖如果项目中已有的话,则不需要重复引入,需要注意的是JUnit 4.4及以上版本的JUnit需要引入2.0.x 版本以上的 powermock 。如果项目中有mockito依赖还需要注意mockito的版本与powermock版本对应关系,对应如下图:详细请参考Using PowerMock with Mockito,如果引入的版本不匹配则可能会报如下错误:

代码语言:javascript复制
java.lang.TypeNotPresentException: Type org.powermock.modules.junit4.PowerMockRunner not present

依赖引入之后就可以编写单元测试代码了。

注解说明

现有一个待测试的类UserServiceImpl,该类中注入了一个UserMapper的类实例。

代码语言:javascript复制
@Service
public class UserServiceImpl {
  @Autowried
    private UserMapper userMapper;
    ........省略部分方法
}

那么如何对上面的类通过powermock的方式进行单元测试呢?首先是定义一个测试类,定义如下:

代码语言:javascript复制
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class})
public class UserServiceImplTest {
   @Mock
    private UserMapper userMapper;
    @InjectMocks
    private UserServiceImpl userServiceImpl = new UserServiceImpl();
}

@RunWith(PowerMockRunner.class) 注解表明使用PowerMockRunner运行测试用例,这个必须添加,不然无法使用PowerMock。@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class}) @PrepareForTest 注解是用来添加所有需要测试的类,这里列举了三个需要测试的类。@Mock注解修饰会mock出来一个对象,这里mock出来的是UserMapper类实例。@InjectMocks 注解会主动将已存在的mock对象注入到bean中,按名称注入,这个注解修饰在我们需要测试的类上。必须要手动new一个实例,不然单元测试会有问题。这几个注解是一个测试类必须要的。说完了测试类的定义,接下来就让我们来看看各种方法是如何mock的。

mock普通方法

  1. 待测试的方法(UserMapper中)
代码语言:javascript复制
 boolean saveUser(User user) {
        int i = userMapper.addUser(user);
        return i == 1 ? true : false;
    }

这里的方法int i = userMapper.addUser(user); 有入参,有出参,没有关键字修饰,是一个普通的方法,mock的方式也很简单,就是PowerMockito.when(userMapper.addUser(user)).thenReturn(1); 在when方法中调用你需要mock的方法,thenReturn方法写入你期待返回的值。从字面意思理解就是当调用xxx方法时,返回xxx值。详细的示例如下:2. 测试方法

代码语言:javascript复制
 User user = new User();
        user.setId(1);
        user.setUserName("test");
        user.setPassword("admin123");
        PowerMockito.when(userMapper.addUser(user)).thenReturn(1);
        boolean result = userServiceImpl.saveUser(user);
        Assert.assertEquals(true, result);

mock抛出异常

单元测试中我们有时候需要mock异常的抛出,其mock的方式也很简单就是在thenThrow(new Exception())写入你期待抛出的异常。如果被mock的方法抛出的是受检异常(checked exception)的话,那么thenThrow抛出new Exception()或者其子类。如果被mock的方法抛出的是非受检异常(unchecked exception),那么thenThrow抛出new RuntimeException或其子类。使用的示范如下:

  1. 待测试的方法(UserMapper中)
代码语言:javascript复制
 int delUser(int id) throws Exception {
        if (id == -1) {
            throw new Exception("传入的id值不对");
        } else {
            return 1;
        }
    }
  1. 测试方法
代码语言:javascript复制
   PowerMockito.when(userMapper.delUser(-1)).thenThrow(new Exception());

这里delUser方法抛出的是受检异常Exception,所以在thenThrow中需要new一个Exception对象。

mock新建对象

如果我们要对一个实体对象Bean进行Mock,只需要这样写PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user)这个代码的意思是创建一个User实例对象,不管传入啥参数都返回定义的实例user,用于替换被测试方法中相应的User对象。使用示范如下:

  1. 待测试的方法(UserMapper中)
代码语言:javascript复制
 public int countUser() {
        User user = new User();
        int count = 0;
        if (user.getId() > 0) {
            count  = 1;
        }
        return count;
    }
  1. 测试方法
代码语言:javascript复制
 // 6.mock新建对象
        User user = new User();
        user.setId(11);
        PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user);
        int result = userServiceImpl.countUser();
        Assert.assertEquals(1, result);

这里mock了一个User对象。id是11,当调用countUser方法时可以拿到之前mock的User对象,所以返回的结果是1。

mock无返回值的方法

对于返回值是通过void修饰的方法,他的mock方式与普通方法的mock方式不同。有两种方式mock。方式一:

代码语言:javascript复制
  PowerMockito.doNothing().when(userMapper, "updateUser", new User());

在when方法中传入userMapper类实例,需要调用的方法名,以及需要传入的参数。方式二:

代码语言:javascript复制
  PowerMockito.doNothing().when(userMapper).updateUser(user);

在when方法中只传入userMapper类实例,然后通过函数式调用的方式调用待测试的方法。使用示范如下:

  1. 待测试的方法(UserServiceImpl中)
代码语言:javascript复制
 public void updateUser(User user) {
        userMapper.updateUser(user);
    }
  1. 测试方法
代码语言:javascript复制
  User user = new User();
        // 4.mock返回值为void的方法
        //方法一
        PowerMockito.doNothing().when(userMapper, "updateUser", new User());
        //方法二
        PowerMockito.doNothing().when(userMapper).updateUser(user);
        userServiceImpl.updateUser(user);

mock被final修饰的方法

现在有一个方法被final关键字修饰,那么该如何要mock这个方法,首先需要mock出一个类实例。如下所示:

代码语言:javascript复制
    UserMapper mock = PowerMockito.mock(UserMapper.class);

这里需要特别注意的是被mock的类必须要在@PrepareForTest注解中指定,如本例中的@PrepareForTest({UserMapper.class})。不然就会报如下错误:

代码语言:javascript复制
org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.

使用示范如下:

  1. 待测试的方法(UserMapper中)
代码语言:javascript复制
    final String getUserName() {
        return "admin";
    }
  1. 测试方法
代码语言:javascript复制
  UserMapper mock = PowerMockito.mock(UserMapper.class);
  when(mock.getUserName()).thenReturn("123");

参数模糊匹配

前面的测试方法中,参数我们都是指定的,在一些场景下,对于一些比较复杂的参数,我们不好构造,这时候参数模糊匹配就派上用场了。如下所示,现有方法selectUser,他有三个参数,参数类型个不相同。

代码语言:javascript复制
User selectUser(Integer id, String userName, String password)

当对这个方法进行mock时,可以不用传入具体的参数值。就行这样进行mock。

代码语言:javascript复制
PowerMockito.when(userMapper.selectUser(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(user);

其中ArgumentMatchers.anyInt()是指任意的int类型的值,ArgumentMatchers.anyString()是指任意String类型的值。需要特别注意的是一个方法中只要有一个参数使用了模糊匹配,其余的参数也都需要使用模糊匹配。

mock静态方法

对静态方法的mock也比较简单,与普通方法的mock相比只是多了一行代码。就是首先需要对静态方法的所在的类进行mock。

代码语言:javascript复制
 PowerMockito.mockStatic(DateUtil.class);

同时被mock的类必须要在@PrepareForTest注解中指定,像本例中的DateUtil类。@PrepareForTest({ DateUtil.class}),其他的与普通方法的mock一样,再此就不在赘述了。

mock私有方法

当我们需要测试的方法中调用了一个比较复杂的私有方法时,我们该如何mock呢?针对这种情况PowerMock也可以轻松应对。首先调用spy方法创建出一个新的UserServiceImpl类实例。然后通过这个实例来mock这个私有方法。如下所示:

代码语言:javascript复制
     UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
  PowerMockito.when(spy, "verifyId", 0).thenReturn(true);

使用示范:

  1. 待测试的方法
代码语言:javascript复制
  boolean delUser(int id) throws Exception {
        int i = userMapper.delUser(id);
        return verifyId(i);
    }
  1. 测试方法
代码语言:javascript复制
   public void testVerifyId() throws Exception {
        // 5.mock私有方法
        UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
        PowerMockito.when(spy, "verifyId", 0).thenReturn(true);
        PowerMockito.when(userMapper.delUser(0)).thenReturn(1);
        Assert.assertEquals(userServiceImpl.delUser(0), true);
    }

总结

本文详细介绍了PowerMock的常见使用,PowerMock是一个应用比较广泛的单元测试框架,运用在单元测试中可以很好的提供测试效率。PowerMock可以mock 普通方法,私有方法,静态方法,final修饰的方法。

参考

无所不能的PowerMock,mock私有方法,静态方法,测试私有方法,final类power mock 入门介绍及使用示例

0 人点赞