1. Mock简介
一个大的项目中,类之间往往是相互依赖的,A类可能依赖B类,B类依赖C类。。。但是我们现在只想测试A类中的方法,如果不用Mock,就需要准备所有其他依赖类,并保证其他依赖类是正常工作的,非常麻烦。但是现在我们可以通过Mock造一个假的正确的B类结果出来,这样就将测试限制在对A类本身中,无需考虑其他依赖类。
SpringBoot的单元测试,可以使用mockito进行mock。但是mockito无法mock static、final这样的方法,因此诞生了PowerMockito。
2.PowerMockito依赖引入
由于PowerMock是Mockito的增强,所以无需额外引用Mockito,而PowerMock依赖需要两个:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
3.与Mockito不同的地方
PowerMockito里面封装了Mockito,所以使用PoweMockito时,也可以使用mockito的方法。@InjectMock和**@Mock**这些都是一样的,但是有一些地方需要改变下:
(1)单元测试测试类上的@RunWith(SpringRunner.class)里面的SpringRunner换成PowerMockRunner。
注意,换成PowerMockRunner后,同一个类下的各个测试不再相互独立,它们公用一套环境,而不是每个测试起自己的环境,要注意是否存在对共有数据的修改。
@RunWith(PowerMockRunner.class)
(2)如果需要注入带有静态方法的类,类上加@PrepareForTest({XXX.class,XXX.class})
@PrepareForTest(RedisUtil.class)
注意:当需要mock系统类的静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解里写的类不是系统类,而是调用系统类的类(一般是待测试类)。
且写在@PrepareForTest 里的类不会被 Jacoco 收集到代码覆盖率!!!
4.单测的写法
(1)mock有返回值的静态方法
mockStatic(XXX.class);
when(XXX.staticMethod(anyString(), anyString())).thenReturn(XX);
(2)mock void的静态方法
mockStatic(XXX.class);
doNothing().when(XXX.class, "voidStaticMethod", any(), any(), any());
(3)mock私有方法
mockPrivateClass = PowerMockito.spy(new MockPrivateClass());
PowerMockito.when(mockPrivateClass, "privateFunc").thenReturn("test");
注意:如果出现下面这个错误
java.lang.LinkageError: loader constraint violation: loader (instance of org/powermock/core/classloader/javassist/JavassistMockClassLoader) previously initiated loading for a different type with name “javax/management/MBeanServer”
只需要在测试类上加以下注解即可(在类上加,仅在某个test上加无效)
@PowerMockIgnore("javax.management.*")
5.实现部分方法Mock
通过@mock标注的类的所有方法都会被mock,不会去执行实际的方法;而通过@InjectMock标注的类是一个注入mock对象的类,待测方法一般位于该类中,它的方法不会被mock掉。如果我们想mock一个@InjectMock类中的方法该怎么办?这时就可以使用部分方法mock。在Mockito和PowerMockito(这两个在这里是完全一样的)中有两种方式实现部分方法mock ,一种使用mock,一种使用spy。
(1) mock实现
使用mock,默认都是不执行实际方法,直接返回设定的返回值;但是如果要执行实际方法,则需要使用doCallRealMethod()或thenCallRealMethod():
//假设TestService在全局是@InjectMock,这里单独重新声明为一个mock类
TestService testService = Mockito.mock(TestService.class); //这个与@Mock作用一样,但是这个不会被注入到InjectMock类中
//不调用实际方法,即不进入该方法中
testService.testMethod(any());
//2种调用实际方法的声明(Mockito和PowerMockito写法一样)
doCallRealMethod().when(testService).testMethod(any());
when(testService.testMethod(any())).thenCallRealMethod();
//PowerMockito的另外两种写法
PowerMockito.doCallRealMethod().when(testService, "testMethod", any());
PowerMockito.when(testService, "testMethod", any()).thenCallRealMethod();
(2)spy实现
使用spy,thenReturn默认是执行实际方法的,只是返回值可以按照设定的进行返回;如果想要不执行实际方法,需要使用doReturn:
TestService testService = Mockito.spy(new TestService());
//2种会执行实际方法
testService.testMethod(any());
when(testService.testMethod(any())).thenReturn("hello");
//不执行实际方法
doReturn("hello").when(testService).testMethod(any()); //有返回值
doNothing().when(testService).testMethod(any()); //无返回值
6.部分方法mock时测试方法还引入了其他service时
当我们通过上面的Mockito.mock(TestService.class)创造了一个新的testService时,该service默认是没有注入任何mock类的,如果测试方法中调用了其他service的方法,那么这个service就会报空指针的问题(因为没有注入)。为解决这个问题,就得重新Mock一个其他类,并通过 PowerMockito.field 注入进测试类中。
PowerMockito.field本是用来mock私有变量的方法,但由于@Autowired标记的一般都是private变量,所以这里通过该方法进行注入。
class TestService {
@Autowierd
private OtherService otherService;
public String testMethod() {
......
String ret = otherService.otherMethod();
......
}
}
TestService testService = PowerMockito.mock(TestService.class);
OtherService otherService = PowerMockito.mock(OtherService.class);
PowerMockito.when(testService.testMethod(any()).thenCallRealMethod();
PowerMockito.field(TestService.class, "otherService").set(testService, otherService);
PowerMockito.when(otherService.otherMathod(any())).thenReturn("xxx");
总之:一般在测试某个类时,该类中被@Autowired注解的Bean都会在测试类中手动通过@Mock注入到InjectMock类中,因此不会出现空指针问题(即找不到该类)。但是通过 PowerMockito.mock() 创建的mock类中没有注入任何其他service,因此要警惕空指针情况(提前通过 PowerMockito.field 进行set)。
7.注意 NullPointerException: can’t unbox null value 问题
有时候测试时懒得造太多数据,给参数设为null,出现了NullPointerException: can’t unbox null value问题。这个问题不注意的话会以为是调用者为null,但其实是调用的方法中实参是null,形参是基本类型,导致出现拆箱问题。(其实就是不能出现 int a = null 这样的问题)