PowerMock 入门

介绍

PowerMock是一个Java模拟框架,用于解决测试问题。

PowerMock 由MockitoEasyMock两部分API构成,它必须要依赖测试框架。

当前PowerMock支持Junit和TestNG.两种测试框架。

针对Junit又有三种不同的执行器对应JUnit4.4+JUnit4.0-4.3JUnit 3

TestNG的执行器实现只有一个版本,但是需要依赖TestNG5.11+版本。

Mock测试

Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。

好处-- 模拟数据

在使用Junit进行单元测试时,不想让测试数据进入数据库,可以使用PowerMock,拦截数据库操作,并模拟返回参数。

好处–减少依赖

代码存在如下依赖:
在这里插入图片描述
当需要测试A类的时候,如果没有Mock,则需要把整个依赖树都构建出来,而使用Mock的话就可以将结构分解开
在这里插入图片描述

包导入

Junit 4.4+ (不同包导入会有差异)

<!-- PowerMock JUnit 4.4+ Module -->
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>
<!-- PowerMock Mockito2 API -->
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-mockito2</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>

示例

/***********************Prepare****************************/
public interface MockMapper {	  
	public int count(MockModel model);
}
 
@Servicepublic 
class MockServiceImpl {	  
	@Autowired	  
	private MockMapper mockMapper;		  

	public int count(MockModel model) {		    
		return mockMapper.count(model);	  
	}
}
/*****************************************************/
@RunWith(PowerMockRunner.class) // 告诉JUnit使用PowerMockRunner进行测试
@PrepareForTest({MockUtil.class}) // 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误
public class MockExample {
    // 将@Mock注解的示例注入进来
    @InjectMocks
    private MockServiceImpl mockService;
    @Mock
    private MockMapper mockMapper;
    
    /**
     * mock普通方法
     */
    @Test
    public void testSelectAppAdvertisementList() {
        MockModel model = new MockModel();
        PowerMockito.when(mockMapper.count(model)).thenReturn(2);
        
        Assert.assertEquals(2, mockService.count(model));
    }
}

常用注解

@RunWith(PowerMockRunner.class) // 告诉JUnit使用PowerMockRunner进行测试
@PrepareForTest({RandomUtil.class}) // 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误

@RunWith(PowerMockRunner.class)

该注解需要在测试类上添加,告诉JUnit使用PowerMock执行器,一般来说都添加上。

@PrepareForTest({UserController.class})

所有需要测试的类,列在此处,以逗号分隔。测试静态方法、私有方法、final方法、使用System/Bootstrap类加载器加载的类和mock构造方法时需要添加该注解。

注意:@PrepareForTest中包含的类不会被LLT统计到代码覆盖。

@PowerMockIgnore(“javax.management.*”)

为了解决使用powermock后,提示classloader错误。

常用行为录制模式

when(…).thenXXX()

doXXX(…).when(mockObj)

Mock静态方法

使用方法

(1)在测试类上加入@RunWith(PowerMockRunner.class)(该注解告诉JUnit使用PowerMock执行器)和@ PrepareForTest(Static.class)注解.

(2)使用PowerMock.mockStatic(Static.class)可以mock Static类中所有的静态方法。也可以使用PowerMockito.spy(class)方法来对特定的方法mock.

(3)调用Mockito.when()来录制行为。

验证行为

要验证静态方法是否有被执行,可以使用PowerMockito.verifyStatic(Mockito.times(1)),每验证一个函数都需要先调用一次PowerMockito.verifyStatic(…)函数:

(1)调用PowerMockito.verifyStatic()方法开始验证行为,不传参数默认是验证调用一次,要校验调用多次传入Mockito.times(…)即可。

(2)PowerMockito.verifyStatic()之后,调用需要验证的静态方法即可完成验证。

PowerMockito.verifyStatic(); // 1

ClassThatContainsStaticMethod. getUserName (param); // 2

使用参数匹配

Mockito的参数匹配仍旧适用于PowerMock

PowerMockito.verifyStatic();

Static.thirdStaticMethod(Mockito.anyInt());

抛出异常

非私有方法
PowerMockito.doThrow(new ArrayStoreException("Mock error")).when(StaticService.class);

私有方法

由于不能直接访问方法,可以将方法名以字符串的形式告诉powermock进行调用

when(tested, "methodToExpect", argument).thenReturn(myReturnValue);

when(tested, "methodToExpect", argument).thenThrow(new ArrayStoreException("Mock error"));

示例

类ClassThatContainsStaticMethod
存在一个静态方法getUserName()返回一个字符串:UserName:id。

package com.cainiaoguoguo.mocktest.staticmethod;

public class ClassThatContainsStaticMethod {

    public static String getUserName(int id)

    {

        return "UserName:" + id;

    }

}

对ClassThatContainsStaticMethod.getUserName()进行mock

package com.cainiaoguoguo.mocktest.staticmethod;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.Mockito;

import org.powermock.api.mockito.PowerMockito;

import org.powermock.core.classloader.annotations.PrepareForTest;

import org.powermock.modules.junit4.PowerMockRunner;


import static org.junit.Assert.*;

 

@RunWith(PowerMockRunner.class)

@PrepareForTest(ClassThatContainsStaticMethod.class)

public class StaticMethodTest {

    @Test

    public void echoUserName() throws Exception {

        final String expectResult = "expectResult";

 

        // mock ClassThatContainsStaticMethod类中所有的静态方法

        PowerMockito.mockStatic(ClassThatContainsStaticMethod.class);

 

        // 改变方法的行为,当调用getUserName的时候返回expectResult

        PowerMockito.when(ClassThatContainsStaticMethod.getUserName(Mockito.anyInt())).thenReturn(expectResult);

        assert(ClassThatContainsStaticMethod.getUserName(123).equals(expectResult));// true

    }

}

首先添加了@RunWith(PowerMockRunner.class)和@PrepareForTest(ClassThatContainsStaticMethod.class)两个注解
然后调用PowerMockito.mockStatic对静态方法进行mock
接下来调用when…then…模式对方法行为进行录制
调用getUserName方法判断返回结果。

综合示例

/************************Prepare****************************/
public class MockUtil {
    private static final Random random = new Random();
    
    public static int nextInt(int bound) {
        return random.nextInt(bound);
    }
}
/***************************************************/

@RunWith(PowerMockRunner.class) // 告诉JUnit使用PowerMockRunner进行测试
@PrepareForTest({MockUtil.class}) // 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误
public class MockExample {
  @Test   
  public void testStaticMethod() {     
  	PowerMockito.mockStatic(MockUtil.class);     
  	PowerMockito.when(MockUtil.nextInt(10)).thenReturn(7);     
  	Assert.assertEquals(7, MockUtil.nextInt(10));   
  }
 }

Mock Final关键字

说明

(1)需要添加@RunWith(PowerMockRunner.class)注解和@PrepareForTest(ClassWithFinal.class)注解

(2)调用PowerMock.mock(ClassWithFinal.class)方法来mock final类的所有方法的,调用该方法将返回一个mock对象(暂且叫他mockObject)。

实例

final类StateHolder
提供一个getState方法返回一个调用其他接口的字符串

package com.cainiaoguoguo.mocktest.finalclass;

 

public final class StateHolder {

    public String getState() {

        return innerMethod("MSG from StateHolder");

    }

 

    private String innerMethod(String msg) {

        return "INNER: " + msg;

    }

}

需要测试的StateFormatter类
引用了StateHolder,要对StateFormatter类中的getFormattedState()方法进行测试

package com.cainiaoguoguo.mocktest.finalclass;

public class StateFormatter {

    private final StateHolder stateHolder;

 

    public StateFormatter(StateHolder stateHolder) {

        this.stateHolder = stateHolder;

    }

 

    public String getFormattedState() {

        String safeState = "State information is missing";

        final String actualState = stateHolder.getState();

        if (actualState != null) {

            safeState = actualState;

        }

        return safeState;

    }

}

测试类
目的要在调用getFormattedState()方法的过程中让StateHolder的getState()方法返回符合预期的值

package com.cainiaoguoguo.mocktest.finalclass;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.powermock.core.classloader.annotations.PrepareForTest;

import org.powermock.modules.junit4.PowerMockRunner;

 

import static org.junit.Assert.assertEquals;

import static org.powermock.api.mockito.PowerMockito.mock;

import static org.powermock.api.mockito.PowerMockito.when;

 

@RunWith(PowerMockRunner.class)

@PrepareForTest(StateHolder.class)

public class StateFormatterTest {

    @Test

    public void getFormattedState() throws Exception {

        final String expectedState = "state";

 

        StateHolder stateHolderMock = mock(StateHolder.class);

        when(stateHolderMock.getState()).thenReturn(expectedState);

 

        StateFormatter tested = new StateFormatter(stateHolderMock);

        String actualState = tested.getFormattedState();

        assertEquals(expectedState, actualState);

    }

}

注意:
(1)需要添加@RunWith(PowerMockRunner.class)和@PrepareForTest(StateHolder.class)注解;

(2)调用PowerMockito.mock()方法mock需要被拦截的类;

(3)使用when…thenReturn…的模式返回期望得到的值。

局部Mock

说明

PowerMockito.spy():不mock一个类中的所有方法,而是mock部分方法

使用场景:当要真实调用某些方法。

注意:
使用spy来录制行为的时候使用when(…).thenXxx(…)的模式会直接调用原有方法,可能会和预期的结果不符合。正确的使用方式是doXXX(…).when(…).methodToMock(…)。

import org.junit.Test;

import org.powermock.api.mockito.PowerMockito;

 

import java.util.ArrayList;

import java.util.List;

 

public class SpyTest {

 

    @Test

    public void testSpy() {

        List list =  new ArrayList();

        List spy = PowerMockito.spy(list);

        // 会抛出IndexOutOfBoundsException异常,因为会去调用spy.get(0)方法。

        PowerMockito.when(spy.get(0)).thenReturn("foo");

 

        // 正确的写法如下

        PowerMockito.doReturn("foo").when(spy).get(0);

    }

}

验证行为

普通公有方法

验证方式和静态方法类似,调用Mockito.vertify()就可以进行标准的行为验证

@Test

public void testSpy() {

    List list =  new ArrayList();

    List spy = PowerMockito.spy(list);

    PowerMockito.doReturn("foo").when(spy).get(0);

 

    spy.get(0);

    spy.get(0);

    Mockito.verify(spy, Mockito.times(2)).get(0); //验证spy.get(0)是否被调用两次

}
验证私有方法

验证私有方法使用PowerMockito.verifyPrivate()即可,该方法对静态私有方法也适用

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.Mockito;

import org.powermock.api.mockito.PowerMockito;

import org.powermock.core.classloader.annotations.PrepareForTest;

import org.powermock.modules.junit4.PowerMockRunner;

 

@RunWith(PowerMockRunner.class)

@PrepareForTest({PartialMockClass.class})

public class SpyTest {

    @Test

    public void verifyPrivate() throws Exception {

        final String expectResult = "MESSAGE";

        String param = "param string";

 

        PartialMockClass classUnderTest = PowerMockito.spy(new PartialMockClass());

        PowerMockito.doReturn(expectResult).when(classUnderTest, "privateMethod", param);

 

        // 调用会执行privateMethod

        assert(classUnderTest.publicMethod(param).equals(expectResult));

        // 使用PowerMockito.verify()验证调用

        PowerMockito.verifyPrivate(classUnderTest, Mockito.times(1)).invoke("privateMethod", param);

    }

}

 

class PartialMockClass {

    public String publicMethod(String msg) {

        return privateMethod(msg);

    }

    private String privateMethod(String msg) {

        return "MSG:" + msg;

    }

}

Mock构造函数

说明

能模拟构造函数从而使被测代码中 new 操作返回的对象可以被随意定制
会很大程度的提高单元测试的效率。

使用的形式是whenNew(MyClass.class).with[No|Any]Arguments().thenXXX(…)

示例

import org.junit.Test;

import org.junit.runner.RunWith;

import org.powermock.api.mockito.PowerMockito;

import org.powermock.core.classloader.annotations.PrepareForTest;

import org.powermock.modules.junit4.PowerMockRunner;

import java.io.File;

 

@RunWith(PowerMockRunner.class)

@PrepareForTest({DirectoryStructure.class})

public class ConstructionTest {

    @Test

    public void testConstruction() throws Exception {

        final String directoryPath = "mocked path";

        File directoryMock = PowerMockito.mock(File.class);

 

        // mock File的构造函数

        PowerMockito.whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);

 

        // 录制行为

        PowerMockito.when(directoryMock.exists()).thenReturn(false);

        PowerMockito.when(directoryMock.mkdirs()).thenReturn(true);

 

        // 调用测试

        assert(new NewFileExample().createDirectoryStructure(directoryPath));

 

        // 验证行为

        PowerMockito.verifyNew(File.class).withArguments(directoryPath);

    }

}

 

class NewFileExample {

    public boolean createDirectoryStructure(String path) {

        DirectoryStructure directoryStructure = new DirectoryStructure();

        return directoryStructure.create(path);

    }

}

 

class DirectoryStructure {

    public boolean create(String directoryPath) {

        File directory = new File(directoryPath);

        if (directory.exists()) {

            throw new IllegalArgumentException("\"" + directoryPath + "\" already exists.");

        }

        return directory.mkdirs();

    }

}
  • 4
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值