这是Spock系列的第七篇文章,本篇主要讲解Spock如何扩展第三方power mock对静态方法进行测试
实现原理
前面的文章讲到Spock的单测代码是继承自Specification基类,而Specification又是基于Junit的注解@RunWith()实现的,代码如下:
@RunWith(Sputnik.class)
@SuppressWarnings("UnusedDeclaration")
public abstract class Specification extends MockingApi {
powermock的PowerMockRunner也是继承自Junit,所以使用powermock的@PowerMockRunnerDelegate()注解可以指定Spock的父类Sputnik去代理运行power mock,这样就可以在Spock里使用powermock去模拟静态方法、final方法、私有方法等
其实Spock自带的GroovyMock可以对groovy文件的静态方法mock,但对Java代码的支持不完整,只能mock当前Java类的静态方法,官方给出的解释如下:
(http://spockframework.org/spock/docs/1.3/all_in_one.html#_mocking_static_methods)
因为我们项目中存在很多调用静态方法的代码,现阶段考虑重构业务代码的成本过高,所以这里使用扩展power mock的方式测试静态方法
Spock 代理 power mock
先看下需要测试的业务代码示例:
public UserVO getUserByIdStatic(int uid){
List<UserDTO> users = userDao.getUserInfo();
UserDTO userDTO = users.stream().filter(u -> u.getId() == uid).findFirst().orElse(null);
UserVO userVO = new UserVO();
if(null == userDTO){
return userVO;
}
userVO.setId(userDTO.getId());
userVO.setName(userDTO.getName());
userVO.setSex(userDTO.getSex());
if("上海".equals(userDTO.getProvince())){
userVO.setAbbreviation("沪");
userVO.setPostCode(200000);
}
if("北京".equals(userDTO.getProvince())){
userVO.setAbbreviation("京");
userVO.setPostCode(100000);
}
if(null != userDTO.getTelephone() && !"".equals(userDTO.getTelephone())){
userVO.setTelephone(userDTO.getTelephone().substring(0,3)+"****"+userDTO.getTelephone().substring(7));
}
// 静态方法调用 身份证工具类
Map<String, String> idMap = IDNumberUtils.getBirAgeSex(userDTO.getIdNo());
userVO.setAge(idMap.get("age")!=null ? Integer.parseInt(idMap.get("age")) : 0);
// 静态方法调用 记录日志
LogUtils.info("response user:", userVO.toString());
return userVO;
}
在倒数第4行和倒数第2行代码分别调用了 "身份证工具类IDNumberUtils.getBirAgeSex()" 和 "LogUtils.info()" 日志记录的方法,如果要对这两个静态方法进行mock,我们可以使用Spock+power mock的方式:
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification
/**
* 测试静态方法mock
* @Author: www.javakk.com
* @Description: 公众号:Java老K
* @Date: Created in 20:53 2020/7/16
* @Modified By:
*/
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([LogUtils.class, IDNumberUtils.class])
@SuppressStaticInitializationFor(["com.javakk.spock.util.LogUtils"])
class UserServiceStaticTest extends Specification {
def processor = new UserService()
def dao = Mock(UserDao)
void setup() {
processor.userDao = dao
// mock静态类
PowerMockito.mockStatic(LogUtils.class)
PowerMockito.mockStatic(IDNumberUtils.class)
}
def "GetUserByIdStatic"() {
given: "设置请求参数"
def user1 = new UserDTO(id:1, name:"张三", province: "上海")
def user2 = new UserDTO(id:2, name:"李四", province: "江苏")
def idMap = ["birthday": "1992-09-18", "sex": "男", "age": "28"]
and: "mock掉接口返回的用户信息"
dao.getUserInfo() >> [user1, user2]
and: "mock静态方法返回值"
PowerMockito.when(IDNumberUtils.getBirAgeSex(Mockito.any())).thenReturn(idMap)
when: "调用获取用户信息方法"
def response = processor.getUserByIdStatic(1)
then: "验证返回结果是否符合预期值"
with(response) {
name == "张三"
abbreviation == "沪"
postCode == 200000
age == 28
}
}
}
在UserServiceStaticTest类的头部使用@PowerMockRunnerDelegate(Sputnik.class)注解,交给Spock代理执行,这样既可以使用 Spock + groovy 的各种功能,又可以使用power mock的对静态,final等方法的mock
@SuppressStaticInitializationFor(["com.javakk.spock.util.LogUtils"])
这行代码的作用是限制LogUtils类里的静态代码块初始化,因为LogUtils类在第一次调用时会加载一些本地资源配置,比较耗费时间,所以可以使用power mock禁止初始化
然后在setup()方法里对两个静态类进行mock设置,PowerMockito.mockStatic(LogUtils.class),PowerMockito.mockStatic(IDNumberUtils.class)
最后在GetUserByIdStatic测试方法里对getBirAgeSex()方法指定返回默认值:PowerMockito.when(IDNumberUtils.getBirAgeSex(Mockito.any())).thenReturn(idMap)
(power mock的具体用法网上资料很多,这里不展开说明)
运行时在控制台会输出:
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
这是powermock的警告信息,不影响运行结果
(完整的源码在公众号: java老k 里回复spock获取)
另外,如果你的单元测试代码不需要对静态方法, final方法mock, 就没必要使用power mock, 使用Spock自带的Mock()就足够了
因为power mock的原理是在编译期通过ASM字节码修改工具修改我们的代码,然后使用自己的classLoader加载,加载的静态方法越多会相应的增加测试时长
下一篇会讲解Spock自带的mock如何和powermock结合使用,从而发挥更强大的作用
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/302.html
我从git上拉去了代码, 但是按照 这个逻辑在自己的项目里面 就无法进行测试
mock自己的第三方调用, 一直会NPE
@fisherman你好,git上的项目都是可以直接运行的,包括maven编译,你可以先和自己的项目对照一下,检查下是否少了pom依赖,或者提供更详细的报错信息,我好帮你定位问题