前言
PowerMock
是一个单元测试框架,可以模拟静态方法,私有方法和final方法等来简化单元测试的编写。本篇文章将结合简单例子对PowerMock
的常用方法进行说明。
准备工作
一. 注解添加与使用场景
在使用PowerMock
时需要针对不同场景添加对应注解,主要是@RunWith
和@PrepareForTest
注解。注解添加和场景对应如下所示。
场景 | 注解 |
---|---|
模拟final方法 | @PrepareForTest ,@RunWith |
模拟静态方法 | @PrepareForTest ,@RunWith |
模拟私有方法 | @PrepareForTest |
使用whenNew | @PrepareForTest ,@RunWith |
二. 使用PowerMock需要添加的依赖
需要引入的依赖如下所示。
org.mockito
mockito-core
2.23.0
test
org.powermock
powermock-api-mockito2
2.0.2
test
org.powermock
powermock-module-junit4
2.0.2
test
引入mockito-core
是为了提供Mockito
功能,主要使用到org.mockito.ArgumentMatchers
参数占位符,部分情况需要使用到org.mockito.BDDMockito
。引入powermock-api-mockito2
和powermock-module-junit4
是为了提供PowerMock
功能,其中powermock-module-junit4
中还引入了hamcrest-core
,主要是使用其提供的org.hamcrest.MatcherAssert.assertThat
和org.hamcrest.Matchers.is
进行断言判断。
在引入依赖时,需要注意核对Mockito
和PowerMock
的版本对应关系,否则会报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() {
return true;
}
}
public class TestObj {
public boolean isTrue() {
Mock mock = new Mock();
return mock.isTrue();
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(TestObj.class)
public class PowerMockTest {
@Test
public void mockWhenNew() throws Exception {
Mock mock = PowerMockito.mock(Mock.class);
PowerMockito.when(mock.isTrue()).thenReturn(false);
PowerMockito.whenNew(Mock.class).withAnyArguments().thenReturn(mock);
TestObj testObj = new TestObj();
assertThat(testObj.isTrue(), is(false));
}
}
在使用whenNew()
时,@PrepareForTest
注解中一定得是被测试类的Class
对象。上面例子中,在被测试类TestObj
的方法中new了一个Mock
类的对象,所以在@PrepareForTest
注解中加入了TestObj.class
,此时whenNew()
才会在TestObj
中生效。
二. 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 answer = new Answer() {
@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()
方法的返回值类型一致,且通过InvocationOnMock
的getArguments()
可以获取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 answer = new Answer() {
@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 answer = new Answer() {
@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 answer = new Answer() {
@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等与外部存在交互的组件的影响。