PowerMock(一):PowerMock的基本使用

文章目录

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

为啥要使用PowerMock

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

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

PowerMock的使用
环境
软件    版本
junit    4.13
powermock    2.0.7
引入依赖

  <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,如果引入的版本不匹配则可能会报如下错误:

java.lang.TypeNotPresentException: Type org.powermock.modules.junit4.PowerMockRunner not present



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

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

@Service
public class UserServiceImpl {
     @Autowried
    private UserMapper userMapper;
    ........省略部分方法
}


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

@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普通方法

    待测试的方法(UserMapper中)

 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. 测试方法

 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或其子类。使用的示范如下:

    待测试的方法(UserMapper中)

 int delUser(int id) throws Exception {
        if (id == -1) {
            throw new Exception("传入的id值不对");
        } else {
            return 1;
        }
    }



    测试方法

   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对象。使用示范如下:

    待测试的方法(UserMapper中)

 public int countUser() {
        User user = new User();
        int count = 0;
        if (user.getId() > 0) {
            count += 1;
        }
        return count;
    }



    测试方法

 // 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。
方式一:

  PowerMockito.doNothing().when(userMapper, "updateUser", new User());



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

  PowerMockito.doNothing().when(userMapper).updateUser(user);


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

    待测试的方法(UserServiceImpl中)

 public void updateUser(User user) {
        userMapper.updateUser(user);
    }

    测试方法

  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出一个类实例。如下所示:

    UserMapper mock = PowerMockito.mock(UserMapper.class);

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

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.

使用示范如下:

    待测试的方法(UserMapper中)

    final String getUserName() {
        return "admin";
    }


    测试方法

  UserMapper mock = PowerMockito.mock(UserMapper.class);
  when(mock.getUserName()).thenReturn("123");


参数模糊匹配

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

User selectUser(Integer id, String userName, String password)



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

PowerMockito.when(userMapper.selectUser(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(user);



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

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

 PowerMockito.mockStatic(DateUtil.class);


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

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

     UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
     PowerMockito.when(spy, "verifyId", 0).thenReturn(true);

   

使用示范:

    待测试的方法

  boolean delUser(int id) throws Exception {
        int i = userMapper.delUser(id);
        return verifyId(i);
    }

 

    测试方法

   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 入门介绍及使用示例





作者:码农飞哥
微信公众号:码农飞哥