PowerMockIto和MockIto是我们使用的Javer的单测框架。也是基于Junit扩展来的,其提供了更强大的一些功能,单测的重要性直接关系到代码质量,这是非常重要的,尤其在迭代快速的互联网公司,开发往往承担了不仅仅是“开发”的工作。熟练掌握测试框架是非常有意义的。
1. 首先是最基本的mock方法
依赖:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
public class UserService {
public String getUserName() {
return "real user name";
}
}
public void testUser() {
UserService userService = Mockito.mock(UserService.class);
Mockito.when(userService.getUserName()).thenReturn("mock name");
System.out.println(userService.getUserName());
}
输出:
mock name
2. @mock 注解方式,类上加上@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.class) // 注意这里别忘记
public class UserMockTest {
@Mock
private UserService userService;
@Test
public void testUser() {
Mockito.when(userService.getUserName()).thenReturn("mock name");
System.out.println(userService.getUserName());
}
}
输出:mock name
3. Spring依赖注入mock测试
主要就是UserService的部分逻辑我需要测试,但是其调用NameService的方法我需要去mock替换。此依赖是基于 Spring的容器管理。我们该怎样去mock呢。
这里要mock. UserService 的 getUserName方法,可能其中有一个注入调用this.nameService.getName()我们需要mock。
可以使用这样的:
public class UserService {
@Resource
private NameService nameService;
public String getUserName() {
/**
* 可测试部分
*/
return this.nameService.getName();
}
}
@RunWith(MockitoJUnitRunner.class)
public class UserMockTest {
@Spy // 真实的类
@InjectMocks // 内部依赖可注入
private UserService userService = new UserService();
@Mock
public NameService nameService;
@Before
public void testBefore() {
Mockito.when(nameService.getName()).thenReturn("mock name service");
}
@Test
public void testUser() {
System.out.println(userService.getUserName());
}
}
输出 mock name service
点评:Spy注解就是说手动new一个类去替换,至于里面的对象是否注入mock的对象,用@InjectMocks去生效。
5. static 方法mock
到这里,就是一些复杂场景了,这里网上你去搜索会发现各种稀奇古怪的报错信息,例如字节码错误,或则是npe或则其他问题,但是最终都会发现跟PowerMockIto和MockIto和字节码甚至是JDK的版本兼容性问题,Github有人po出了对象版本的对象关系,仅供参考:
https://github.com/powermock/powermock/wiki/Mockito
我也是调试了很久,最终能成功跑起来的依赖如下:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.3</version>
<scope>test</scope>
</dependency>
如果报java.lang.LinkageError: loader constraint violation: loader (instance of org/powermock/core/classloader/MockClassLoader) previously initiated loading for a different type with name “javax/management/MBeanServer” 类似的错误,一般都是JDK或则其它三方库兼容性问题,可以采用 @PowerMockIgnore(“javax.management.*”) 解决
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*") // 忽略加载包下面的类
@PrepareForTest({RequestContextHolder.class}) // 需要 mock 类静态方法,需要在此声明
static方法的mock如下:
public class UserService {
public static String getStaticName() {
return "real staticName";
}
}
外面调用static方法希望mock。可以如下的方法
@RunWith(PowerMockRunner.class) // 此处指定 runner
@PrepareForTest(UserService.class) // 此处指定static方法的类,可以是数组
public class UserMockTest /*extends BaseTest*/ {
@Before
public void before() {
PowerMockito.mockStatic(UserService.class);
PowerMockito.when(UserService.getStaticName()).thenReturn("mock static user name");
}
@Test
public void testUser() {
System.out.println(UserService.getStaticName());
}
}
输出:mock static user name
6. mock对象的继承 构建模块化集成测试基础
class BaseTest {
}
@RunWith(PowerMockRunner.class)
public class UserMockTest extends BaseTest{
@Mock
public UserService userService;
@Before
public void before() {
PowerMockito.when(userService.getUserName()).thenReturn("sub mock name");
}
@Test
public void testUser() {
System.out.println(userService.getUserName());
}
}
最开始没改任何依赖,直接报错,可以看到我只是添加了一个继承关系而已。
查找了很多资料:
意思就是javassist的版本校验问题,建议使用最新的版本javassist。
随后添加最新的javassist版本然后问题得到解决。:
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
但是这里javassist在maven中央仓库会发现很多版本,看清楚groupId和artifcatId。
- 这里有个问题,如果父类和子类都mock了,会以哪个值为准呢。如下
/**
* 父类继承mock
*/
class BaseTest {
@Mock
public UserService userService;
@Before
public void before() {
PowerMockito.when(userService.getUserName()).thenReturn("base mock name");
}
}
@RunWith(PowerMockRunner.class)
public class UserMockTest extends BaseTest{
@Before
public void before() {
PowerMockito.when(userService.getUserName()).thenReturn("sub mock name");
}
@Test
public void testUser() {
System.out.println(userService.getUserName());
}
}
输出:sub mock name
debug发现,因为子类before重写了,所以父亲before没执行而已。
- 如果子类与父类重复mock:
@RunWith(PowerMockRunner.class)
public class UserMockTest extends BaseTest{
/**
* 先执行
*/
@Before
public void before2() {
PowerMockito.when(userService.getUserName()).thenReturn("sub mock name");
}
@Test
public void testUser() {
System.out.println(userService.getUserName());
}
}
/**
* 父类继承mock
*/
class BaseTest {
@Mock
public UserService userService;
/**
* 后执行
*/
@Before
public void before() {
PowerMockito.when(userService.getUserName()).thenReturn("base mock name");
}
}
输出:base mock name
说明@Before注解是先执行子类的方法,再执行父类的方法。
- 如果子类注入相同类并重复mock会出现怎样的情况
@RunWith(PowerMockRunner.class)
public class UserMockTest extends BaseTest{
// 子类重新定义了userService
@Mock
public UserService userService;
/**
* 先执行
*/
@Before
public void before2() {
/**
* 子类重复mock
*/
PowerMockito.when(userService.getUserName()).thenReturn("sub mock name");
}
@Test
public void testUser() {
System.out.println(userService.getUserName());
}
}
/**
* 父类继承mock
*/
class BaseTest {
@Mock
public UserService userService;
/**
* 后执行
*/
@Before
public void before() {
PowerMockito.when(userService.getUserName()).thenReturn("base mock name");
}
}
输出:sub mock name.
Debug看对象实例,发现子类与父类的userService并不是一个对象。所以子类的调用并不是父类的对象。UserMockTest
里mock的userService也是mock的子类引用实例的对象。如果改成
@Before
public void before2() {
/**
* 子类重复mock
*/
PowerMockito.when(super.userService.getUserName()).thenReturn("sub mock name");
}
则输出 null.
因为testUser里调用的是子类的对象,该对象并没被mock方法。
7. Private方法mock
public class UserService {
public String callPrivateMethodGetName() {
return getPrivateMethodName();
}
private String getPrivateMethodName() {
return "real private name";
}
}
测试
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)// 注意这里要指定private方法的类
public class UserMockTest {
@Spy
public UserService userService = new UserService();
@Before
public void before2() {
try {
PowerMockito.doReturn("mock private name").when(userService, "getPrivateMethodName");
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testUser() {
System.out.println(userService.callPrivateMethodGetName());
}
}
输出:mock private name
8. Mock范性返回
class UserService {
public <T> T getUser() {
return null;
}
}
interface User {
String getIdentify();
}
class Manager implements User {
@Override
public String getIdentify() {
return "real manager";
}
}
class Employee implements User {
@Override
public String getIdentify() {
return "real employee";
}
}
@RunWith(PowerMockRunner.class)
public class UserMockTest {
@Mock
public UserService userService;
@Before
public void before2() {
try {
PowerMockito.doReturn((User) () -> "mock user").when(userService).getUser();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testUser() {
User user = userService.getUser();
System.out.println(user.getIdentify());
}
}
输出:mock user
点评:关键在于doReturn的方式, when的对象的方法在when的返回值OngoingStubbing里。
补充:如果是final类mock的话也需要添加到PrepareForTest类标里面,否则会报初始化错误。
以上就是PowerMock的mock示范,这些场景基本能够覆盖绝大部分的case,个人感觉PowerMock的兼容性做的很差并且依赖的二方库也比较多,虽然有easymock这样的快速继承框架,但是感觉也是个beta版。