mockito大家都比较熟悉了,存在或者不存在,都不要紧,mockito让你有一种只要一出手,就知道有没有的感觉。但是它也不是万能的,比如静态方法、私有方法,它就无能为力了。这是为什么呢?当然不是mockito的框架或现有技术解决不了,而是出于某些原因或立场,比如测试理念观点。甚至在mockito的FAQ中,作者明确了每一项未实现的功能不支持的原因,或者干脆说已经有别的工具实现了,需要的话,去用那个工具吧,我不愿意重复造轮子。
当然实现这些也并非轻而意举,比如如何mock final类,特别是jdk中的final类,比如String。但作为系统类,在任何时候都不应该可以被修改(即使是有办法修改,也不建议去修改,也没有必要修改,否则重新设计一门新语言即可),特别是对于java.lang包下的类,如基本的数据类型Integer、Long等。java agent可以修改由AppClassLoader加载的类,而endorsed技术也只允许覆盖在有限的限制列表中的类。而powermock采取的方案是,如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
mockito的实现原理是用asm给需要mock的对象生成对应的代理对象,然后使用mock出来的对象即可。而在spring框架中,SpyBean与MockBean的原理也是一样,只不过还需要多做一步,就是用mock后的对象替换容器中原有的对象。
一、为什么要使用PowerMock
现如今比较流行的Mock工具如jMock 、EasyMock 、Mockito等都有一个共同的缺点:不能mock静态、final、私有方法等。而PowerMock能够完美的弥补以上三个Mock工具的不足。
二、PowerMock简介
PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。通过使用自定义的类加载器,简化采用的IDE或持续集成服务器不需要做任何改变。熟悉PowerMock支持的mock框架的开发人员会发现PowerMock很容易使用,因为对于静态方法和构造器来说,整个的期望API是一样的。PowerMock旨在用少量的方法和注解扩展现有的API来实现额外的功能。目前PowerMock支持EasyMock和Mockito。
三、PowerMock入门
PowerMock有两个重要的注解:
–@RunWith(PowerMockRunner.class)
–@PrepareForTest( { YourClassWithEgStaticMethod.class })
如果有调用其他类中的静态方法,则需要指定被mock的静态资源类,没有的话则不需要
如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。
先给出代码示例:
package cn.susoncloud.wisdomclass.usercenter; import cn.susoncloud.wisdomclass.usercenter.domain.service.TenantService; import cn.susoncloud.wisdomclass.usercenter.domain.service.UserService; import cn.susoncloud.wisdomclass.usercenter.interfaces.assembler.LoginAssembler; import cn.susoncloud.wisdomclass.usercenter.interfaces.assembler.RedisUserAssembler; import cn.susoncloud.wisdomclass.usercenter.interfaces.dto.UserLoginPwd; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunnerDelegate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.powermock.api.mockito.PowerMockito; import static org.mockito.ArgumentMatchers.any; /** * @Description: 工具测试方法 * @Author: huminghao * @Date: 2020-02-03 */ @ActiveProfiles("testDevelopSql") @SpringBootTest @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringRunner.class) @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"}) @PrepareForTest(LoginAssembler.class) public class DemoTest { @Autowired RedisUserAssembler redisUserAssembler; @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private UserService userService; @Autowired private TenantService tenantService; /** * @Description: 根据手机号获取token,mock掉了其余步骤,如密码解密验证。 * @Return: void * @Author: huminghao * @Date: 2020/2/3 */ @Test public void getTokenForTestByPwd() throws Exception { LoginAssembler loginAssembler = new LoginAssembler(); loginAssembler.setRedisUserAssembler(redisUserAssembler); loginAssembler.setRedisTemplate(redisTemplate); loginAssembler.setUserService(userService); loginAssembler.setTenantService(tenantService); LoginAssembler spyLoginAssembler = PowerMockito.spy(loginAssembler); PowerMockito.doNothing().when(spyLoginAssembler, "checkLoginPwd", any(), any()); UserLoginPwd userLoginPwd = new UserLoginPwd(); userLoginPwd.setPhoneNumber(4L); userLoginPwd.setPassword(""); userLoginPwd.setClientId(""); userLoginPwd.setLoginChannel(2); System.out.println(spyLoginAssembler.pwdLogin(userLoginPwd)); } }
在 Spring Boot 的测试套件中,需要添加 @RunWith(SpringRunner.class) 和 @SpringBootTest 注解。
但是 PowerMock 需要添加 @RunWith(PowerMockRunner.class) 注解。
@RunWith 注解只能有一个,解决方案是使用 @PowerMockRunnerDelegate 注解,同时使用 @PowerMockIgnore 注解避免报错。
上述示例中,因为我们需要mock掉LoginAssembler的私有方法checkLoginPwd,无论传入什么参数,都不进行真实调用,避免真实的密码校验逻辑,而其他方法正常真实调用。所以先@PrepareForTest指定私有方法的类,然后spy(注意这里的spy和spring的@SpyBean行为并不一致,他不会容器注入,需要我们手动bean管理,如上代码该类的属性均为手动赋值,这样才能保证我们其他方法真实调用时,不会出错。因为方法无返回值,所以doNothing,注意doNothing需要指定在when之前,否则when中指定的方法,会先真实调用一次。any为mockito的api)
四、PowerMock简单实现原理
• 当某个测试方法被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类(系统类除外)。
• PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
• 如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
powermock使用了自定义的classloader来解决mock静态方法与私有方法的问题,因此其会为加了PrepareForTest注解的类生成对应的classloader来加载用到的类,这样就可能会导致其与系统的classloader加载了相同的类,导致类型转换失败,PowerMockIgnore注解则是告诉powermock放弃加载指定的这些类。
参考:PowerMock 精萃
发表评论