PowerMock踩坑指南

单元测试踩过无数的坑,都源于源代码写的不好,但是如何在不修改源代码的同时把单元测试写好,是我这个实习生应该摸索的,下面把我遇到的坑和PowerMock的一些方法做个总结。
首先贴个官方文档:
https://github.com/powermock/powermock
所有用到的方法文档里都有,关键在于如何组合,如果使用。
一般来说,如果你使用到PowerMock框架来进行单元测试的时候,一定是你遇到了待测试的类是一个静态类,或者待测试的类里有一堆静态方法,又或者里面含有final、私有方法等,那么这时候PowerMock能帮上很多忙。
首先在maven中添加依赖,然后在测试类中使用@RunWith(PowerMockRunner.class)注解。
第一类坑:待测的类中的某方法中调用了其他类的静态方法。
这是最简单的坑,只要使用:

mockStatic(Astatic.class)
Astatic astiatic = mock(Astatic.class)
when(astatic.对应的静态方法()).thenReturn();

第二类坑:待测试类初始化时自带静态方法。

public class A{
  private OkHttpClient httpClient = new OkHttpClient();
  private String str = XXUtil.getinstance().getValue();
} 

XXUtil类自身并不是一个静态类,但它的初始化中也含有静态变量以及它的getinstance()方法也是一个静态方法。而OkHttpClient中也存在大量方法,如果依旧使用@InjectMocks注解对类A进行注入则会报以下错误:
java.lang.RuntimeException: Invoking the beforeTestMethod method on PowerMock test listener org.powermock.api.extension.listener.AnnotationEnabler@6d91790b failed.
为了解决这个错误,我们需要做以下几步:
1.首先我们要知道,单元测试仅仅测试当前类,只要调用了其他类的方法,那么那些类都要统统Mock掉。
2.其次,不要注释注入的形式来创建测试类,要使用最原始创建类的方法。

@RunWith(PowerMockRunner.class)
@PrepareForTest({XXUtile.class,OkHttpClient.class})
@PowerMockIgnore({ "javax.xml.*",  
"javax.management.*","com.sun.org.apache.xerces.*","javax.net.ssl.*"})
public class A{
    @Test
    public void Amethod(){
        A a = new A();
        ReflectionTestUtils.setField(a,"str","abc");
    }
}

当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest,将所有的需要Mock的类都填上.
@PowerMockIgnore这个注解很奇怪,如果不加,就有可能报以下错误:
java.lang.IllegalAccessError: class javax.xml.parsers.FactoryFinder (in unnamed module @0x5ec77191) cannot access class jdk.xml.internal.SecuritySupport (in module java.xml) because module java.xml does not export jdk.xml.internal to unnamed module @0x5ec77191
网上查到的解释也不甚清楚,个人理解为PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。对应的框架尝试使用反射实例化类,并从线程上下文类加载器(PowerMock的类加载器)执行此操作,但随后尝试将创建的对象分配给未由同一类加载器加载的字段。所以需要@PowerMockIgnore注释来告诉PowerMock将某个包的加载推迟到系统类加载器。您需要忽略的是特定于案例,但通常是XML框架或与其交互的一些包。
随后不能使用InjectMocks注入,要在测试方法中实例化测试类,并通过反射的方法对之前抑制初始化的参数赋值。
注意,如果类初始化中的参数实例化使用的XXUtile类中的构造函数若为私有,则需使用suppress(constructor(XXUtile.class))进行抑制,否则会报错。

@RunWith(PowerMockRunner.class):在测试用例的类级别使用注释。
@PrepareForTest(ClassWithEvilParentConstructor.class):在测试用例的类级别结合使用注释,suppress(constructor(EvilParent.class))以抑制EvilParent类的所有构造函数。使用该Whitebox.newInstance(ClassWithEvilConstructor.class):方法实例化一个类而不调用构造函数使用
@SuppressStaticInitializationFor("org.mycompany.ClassWithEvilStaticInitializer"):注释删除类的静态初始化程序org.mycompany.ClassWithEvilStaticInitializer。
@PrepareForTest(ClassWithEvilMethod.class):在测试用例的类级别结合使用注释,suppress(method(ClassWithEvilMethod.class, "methodName"))以在ClassWithEvilMethod类中禁止名为“methodName”的方法。
@PrepareForTest(ClassWithEvilField.class):在测试用例的类级别结合使用注释,suppress(field(ClassWithEvilField.class, "fieldName"))以在ClassWithEvilField类中禁止名称为“fieldName”的字段。

这是官方文档中给出的几种抑制方法,视情况合理使用。
第三类坑:我们希望Mock的一个类,定义了一个static块,其中又调用了私有的静态方法。在这个私有静态方法中,依赖了其他的一些对象,这些对象还牵扯到服务容器的问题。即使以静态的方式Mock了该类,仍然逃不过运行static块的命运,换言之,仍然需要依赖服务容器。
这时候我们可以使用@SuppressStaticInitializationFor,在该注解中需要传入字符串类型的目标类型的全名。
注意,该注解不能对测试类进行使用,否则会导致单元测试覆盖率为0的情况产生
第四类坑:需要做单元测试的类自身是静态类,或含静态方法,或两者皆有之
这个问题的解决办法其实和第二类坑差不多,只不过强调一点:

String s = XXX.builder().
              name().
              build().
              create();

在遇到这一类方法时,要对每一层都进行mock,因为会依赖与XXX这个类,所以建议直接将这个类的方法抽象出来

private void mockXXXBuilder(String S) {    
        XXX.Builder XXXBuilderMock = mock(XXX.Builder.class);    
        when(XXXBuilderMock.build()).thenReturn(XXXBuilderMock);
        when(XXXBuilderMock.name(anyString())).thenReturn(XXXBuilderMock);
        when(XXX.builder()).thenReturn(XXXBuilderMock);
        when(XXXBuilderMock.create(anyString())).thenReturn(XXXBuilderMock);

诸如此。
最后强调一点,若mock静态类,可以采用以下这种形式:

mockStatic(XXXUtil.class);
String str = mock(String.class)
when(String.format(any(),any(),any())).thenReturn(str);

---------------------------------------------------------------------------

PowerMockito.mockStatic(String.class);
when(String.format(any(),any(),any())).thenReturn("abcdefg");

两种方法,自由选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容