乐趣区

关于单元测试:单元测试PowerMock

前言

PowerMock是一个单元测试框架,能够模仿静态方法,公有办法和 final 办法等来简化单元测试的编写。本篇文章将联合简略例子对 PowerMock 的罕用办法进行阐明。

筹备工作

一. 注解增加与应用场景

在应用 PowerMock 时须要针对不同场景增加对应注解,次要是 @RunWith@PrepareForTest注解。注解增加和场景对应如下所示。

场景 注解
模仿 final 办法 @PrepareForTest@RunWith
模仿静态方法 @PrepareForTest@RunWith
模仿公有办法 @PrepareForTest
应用 whenNew @PrepareForTest@RunWith

二. 应用 PowerMock 须要增加的依赖

须要引入的依赖如下所示。

<dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>2.23.0</version>
        <scope>test</scope>
</dependency>
<dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.2</version>
        <scope>test</scope>
</dependency>
<dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.2</version>
        <scope>test</scope>
</dependency>

引入 mockito-core 是为了提供 Mockito 性能,次要应用到 org.mockito.ArgumentMatchers 参数占位符,局部状况须要应用到 org.mockito.BDDMockito。引入powermock-api-mockito2powermock-module-junit4是为了提供 PowerMock 性能,其中 powermock-module-junit4 中还引入了 hamcrest-core,次要是应用其提供的org.hamcrest.MatcherAssert.assertThatorg.hamcrest.Matchers.is进行断言判断。
在引入依赖时,须要留神核查 MockitoPowerMock的版本对应关系,否则会报 java.lang.ClassNotFoundException: org.mockito.exceptions.Reporter 谬误。版本对应关系能够去 PowerMock 官网进行查问:PowerMock 官网,通常状况下,如果引入的 mockito-core 版本为 2.x,则 PowerMock 的 api 须要应用powermock-api-mockito2

注释

一. mock public 办法

public class Mock {public boolean isTrue_1() {return true;}

}

public class PowerMockTest {

    @Test
    public void mockPublic() {Mock mock = PowerMockito.mock(Mock.class);
        PowerMockito.when(mock.isTrue_1()).thenReturn(false);
        assertThat(mock.isTrue_1(), is(false));
    }

}

mock public 办法时须要应用 PowerMockito.mock(办法所在类.class) 获取 mock 进去的对象,这里称之为 mock 实例,mock 实例的办法均为假办法,不对 mock 实例进行任何操作的状况下,调用 mock 实例的办法会返回(如果有返回值的话)返回值类型的默认值(零值,比方 String 返回 null,Integer返回 0)。如果想要调用 mock 实例的办法时使其执行实在办法,那么打桩时须要应用thenCallRealMethod(),如下所示。

public class Mock {public boolean isTrue_1() {return true;}

}

public class PowerMockTest {

    @Test
    public void mockPublicThenCallRealMethod() {Mock mock = PowerMockito.mock(Mock.class);
        PowerMockito.when(mock.isTrue_1()).thenCallRealMethod();
        assertThat(mock.isTrue_1(), is(true));
    }

}

同时能够应用 whenNew() 来实现在程序中 new 一个对象时失去一个 mock 实例,如下所示。

public class Mock {public boolean isTrue_1() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockWhenNew() throws Exception {Mock mock_1 = PowerMockito.mock(Mock.class);
        PowerMockito.when(mock_1.isTrue_1()).thenReturn(false);
        PowerMockito.whenNew(Mock.class).withAnyArguments().thenReturn(mock_1);
        Mock mock_2 = new Mock();
        assertThat(mock_2.isTrue_1(), is(false));
    }

}

下面的例子中实现了在调用 Mock 对象的构造函数时返回预置好的 mock 实例(下面例子中为 mock_1),因而 mock_2 实例理论指向了 mock_1 实例,调用 mock_2.isTrue_1()办法时会返回 false。

二. mock final public 办法

和 mock public 办法操作统一。如下所示。

public class Mock {public final boolean isTure_2() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockFinalPublic() {Mock mock = PowerMockito.mock(Mock.class);
        PowerMockito.when(mock.isTure_2()).thenReturn(false);
        assertThat(mock.isTure_2(), is(false));
    }

}

三. mock private 办法

public class Mock {public boolean isTrue_3() {return returnTrue_1();
    }
    
    private boolean returnTrue_1() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockPrivate() throws Exception {Mock mock = PowerMockito.mock(Mock.class);
        PowerMockito.when(mock, "returnTrue_1").thenReturn(false);
        PowerMockito.when(mock.isTrue_3()).thenCallRealMethod();
        assertThat(mock.isTrue_3(), is(false));
    }

}

mock private 办法打桩时,须要应用 PowerMockito.when(mock 实例,"公有办法名").thenReturn(冀望返回值) 的模式设置 mock 实例的公有办法的返回值,如果公有办法有参数,还须要在公有办法名前面增加参数占位符,比方 PowerMockito.when(mock 实例,"公有办法名",anyInt()).thenReturn(冀望返回值)。下面例子中进行断言时,调用公有办法采取了调用公共办法来间接调用公有办法的模式,单元测试代码对业务代码造成了入侵,因而如果仅仅只是为了验证一个公有办法,能够应用Whitebox 来不便的调用公有办法,如下所示。

public class Mock {private boolean returnTrue_1() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockPrivate() throws Exception {Mock mock = PowerMockito.mock(Mock.class);
        PowerMockito.when(mock, "returnTrue_1").thenReturn(false);
        assertThat(Whitebox.invokeMethod(mock, "returnTrue_1"), is(false));
    }

}

四. mock static public 办法

通常是针对工具类进行 mock。如下所示。

public class Mock {public static boolean isTrue_4() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockStaticPublic() {PowerMockito.mockStatic(Mock.class);
        PowerMockito.when(Mock.isTrue_4()).thenReturn(false);
        assertThat(Mock.isTrue_4(), is(false));
    }

}

五. mock static private 办法

public class Mock {public static boolean isTrue_5() {return returnTrue_2();
    }
    
    private static boolean returnTrue_2() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockStaticPrivate() throws Exception {PowerMockito.mockStatic(Mock.class);
        PowerMockito.when(Mock.class, "returnTrue_2").thenReturn(false);
        PowerMockito.when(Mock.isTrue_5()).thenCallRealMethod();
        assertThat(Mock.isTrue_5(), is(false));
    }

}

同样也能够应用 Whitebox 来不便的调用动态公有办法,如下所示。

public class Mock {private static boolean returnTrue_2() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void mockStaticPrivate() throws Exception {PowerMockito.mockStatic(Mock.class);
        PowerMockito.when(Mock.class, "returnTrue_2").thenReturn(false);
        assertThat(Whitebox.invokeMethod(Mock.class, "returnTrue_2"), is(false));
    }

}

六. Whitebox

应用 Whitebox 能够不便的设置对象(动态)公有属性值,如下所示。

public class Mock {

    private boolean flag = true;
    
    public boolean isTrue_6() {return flag;}

}

public class PowerMockTest {

    @Test
    public void whiteboxPrivateField() {Mock mock = new Mock();
        Whitebox.setInternalState(mock, "flag", false);
        assertThat(mock.isTrue_6(), is(false));
    }

}

仅应用 Whitebox 时不须要增加 @RunWith@PrepareForTest注解,同时对于下面例子如果 flag 是动态变量,那么设置动态变量值时须要应用 Whitebox.setInternalState(Mock.class, "flag", false)。应用Whitebox 也能够不便的调用对象(动态)公有办法,如下所示。

public class Mock {private boolean isTrue_7() {return true;}

}

public class PowerMockTest {

    @Test
    public void whiteboxPrivateMethod() throws Exception {Mock mock = new Mock();
        assertThat(Whitebox.invokeMethod(mock, "isTrue_7"), is(true));
    }

}

对于下面例子,如果 isTrue_7()是动态公有办法,那么调用动态公有办法时的语句为:assertThat(Whitebox.invokeMethod(Mock.class, "isTrue_7").is(true))

七. Answer-mock

针对同一办法屡次被调用且不同入参须要 mock 不同出参的状况,能够应用Answer,如下所示。

public class Mock {public String isTrue_8(int num) {return "";}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void answer() {Mock mock = PowerMockito.mock(Mock.class);
        Answer<String> answer = new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) {int num = (Integer) invocation.getArguments()[0];
                if (num == 0) {return "zero";} else if (num == 1) {return "one";}
                return null;
            }
        };
        PowerMockito.when(mock.isTrue_8(anyInt())).thenAnswer(answer);
        assertThat(mock.isTrue_8(0), is("zero"));
        assertThat(mock.isTrue_8(1), is("one"));
    }

}

其中 Answer 的泛型类型须要与 answer() 办法的返回值类型统一,且通过 InvocationOnMockgetArguments()能够获取 mock 实例调用的办法所有入参,callRealMethod()能够调用实在办法,getMethod()能够获取 mock 实例调用的办法,getMock()能够获取 mock 实例。同时,还能够应用 org.mockito.BDDMockito.given 来实现雷同的成果,如下所示。

public class Mock {public String isTrue_8(int num) {return "";}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {

    @Test
    public void answer() {Mock mock = PowerMockito.mock(Mock.class);
        Answer<String> answer = new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) {int num = (Integer) invocation.getArguments()[0];
                if (num == 0) {return "zero";} else if (num == 1) {return "one";}
                return null;
            }
        };
        given(mock.isTrue_8(anyInt())).willAnswer(answer);
        assertThat(mock.isTrue_8(0), is("zero"));
        assertThat(mock.isTrue_8(1), is("one"));
    }

}

八. spy public 办法

public class Spy {public boolean isTrue_1() {return true;}
    
    public boolean isTrue_2() {return true;}

}

public class SpyTest() {

    @Test
    public void spyPublic() {Spy spy = PowerMockito.spy(new Spy());
        PowerMockito.doReturn(false).when(spy).isTrue_1();
        assertThat(spy.isTrue_1(), is(false));
        assertThat(spy.isTrue_2(), is(true));
    }

}

spy public 办法时须要应用 PowerMockito.spy(办法所在类的实例) 获取 spy 进去的对象,这里称之为 spy 实例,不对 spy 实例进行任何操作的状况下,spy 实例与实在实例是齐全一样的。同时因为 spy 实例与实在实例齐全一样,因而在对 spy 实例进行打桩时应用 doReturn()thenReturn()是存在差异的:应用 doReturn(返回值) 时不会执行实在形式,间接返回返回值;应用 thenReturn(返回值) 时会先执行一遍实在办法,而后返回返回值。通常状况下 spy 须要配合 doReturn() 应用,用于克制实在办法的执行,避免执行实在办法时报错。
同时,打桩时应用 doReturn()thenReturn()的语法存在差异,下面例子中打桩时如果应用的语句为 PowerMockito.doReturn(false).when(spy.isTrue_1()),会导致编译时失常,运行时报错的景象。下表对打桩时doReturn()thenReturn()的语法进行了比照。

应用场景 doReturn() thenReturn()
打桩 public 办法 PowerMockito.doReturn(false).when(spy).isTrue_1() PowerMockito.when(spy.isTrue_1()).thenReturn(false)
打桩 private 办法 PowerMockito.doReturn(false).when(spy, "returnTrue_1") PowerMockito.when(spy, "returnTrue_1").thenReturn(false)
打桩 static 办法 PowerMockito.doReturn(false).when(Spy.class, "isTrue_5") PowerMockito.when(Spy.isTrue_5()).thenReturn(false)
打桩 static private 办法 PowerMockito.doReturn(false).when(Spy.class, "returnTrue") PowerMockito.when(Spy.class, "returnTrue").thenReturn(false)

最初,spy 也和 mock 一样,能够配合 whenNew() 进行应用。

九. spy private 办法

public class Spy {public boolean isTrue_4() {return returnTrue_1();
    }
    
    private boolean returnTrue_1() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Spy.class)
public class SpyTest() {

    @Test
    public void spyPrivate() throws Exception {Spy spy = PowerMockito.spy(new Spy());
        PowerMockito.doReturn(false).when(spy, "returnTrue_1");
        assertThat(spy.isTrue_4(), is(false));
    }

}

十. spy static public 办法

public class Spy {public static boolean isTrue_5() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Spy.class)
public class SpyTest() {

    @Test
    public void spyStaticPublic() throws Exception {PowerMockito.spy(Spy.class);
        PowerMockito.doReturn(false).when(Spy.class, "isTrue_5");
        assertThat(Spy.isTrue_5(), is(false));
    }

}

十一. spy static private 办法

public class Spy {public static boolean isTrue_6() {return returnTrue();
    }
    
    private static boolean returnTrue_2() {return true;}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Spy.class)
public class SpyTest() {

    @Test
    public void spyStaticPrivate() throws Exception {PowerMockito.spy(Spy.class);
        PowerMockito.doReturn(false).when(Spy.class, "returnTrue_2");
        assertThat(Spy.isTrue_6(), is(false));
    }

}

十二. Answer-spy

public class Spy {public String isTrue_7(int num) {return "";}

}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Spy.class)
public class SpyTest() {

    @Test
    public void answer() {Spy spy = PowerMockito.spy(new Spy());
        Answer<String> answer = new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) {int num = (Integer) invocation.getArguments()[0];
                if (num == 0) {return "zero";} else if (num == 1) {return "one";}
                return "";
            }
        };
        PowerMockito.doAnswer(answer).when(spy).isTrue_7(anyInt());
        assertThat(spy.isTrue_7(0), is("zero"));
        assertThat(spy.isTrue_7(1), is("one"));
    }
    
    @Test
    public void given_when_then_bdd() {Spy spy = PowerMockito.spy(new Spy());
        Answer<String> answer = new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) {int num = (Integer) invocation.getArguments()[0];
                if (num == 0) {return "zero";} else if (num == 1) {return "one";}
                return "";
            }
        };
        given(spy.isTrue_7(anyInt())).willAnswer(answer);
        assertThat(spy.isTrue_7(0), is("zero"));
        assertThat(spy.isTrue_7(1), is("one"));
    }

}

总结

正当应用 PowerMock 能够解决单元测试编写中的一些常见难题,在解依赖时能够帮忙咱们躲避例如数据库,kafka 等与内部存在交互的组件的影响。

退出移动版