您好,我是码农飞哥,感谢您阅读本文!本文主要介绍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 |
引入依赖
代码语言: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普通方法
- 待测试的方法(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。方式一:
代码语言:javascript复制 PowerMockito.doNothing().when(userMapper, "updateUser", new User());
在when方法中传入userMapper类实例,需要调用的方法名,以及需要传入的参数。方式二:
代码语言:javascript复制 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出一个类实例。如下所示:
代码语言:javascript复制 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,他有三个参数,参数类型个不相同。
代码语言: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);
使用示范:
- 待测试的方法
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 入门介绍及使用示例