1.Mock是什么?

通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。

2.为什么要用PowerMock?

举个例子:当测试单机应用的时候,直接写Junit单元测试即可,但当涉及到多个服务时,你写好了你的服务,其它服务尚未完成,这时候就需要模拟调用远程服务,也就需要Mock。

3.Mock的流程

简单来说,模拟测试一共分为4步:数据准备、打桩(Mock)、执行、验证。

数据准备阶段可以为Mock阶段的准备期望值、参数等数据,执行mock对象的方法后,最后进行验证与判断。

@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivatePartialMockingExample.class)
public class PowerMockTest {
    @Test
    public void demoPrivateMethodMocking() throws Exception {
        //数据准备
        final String expected = "TEST VALUE";

        PrivatePartialMockingExample underTest = PowerMockito.spy(new PrivatePartialMockingExample());

        // mock || 打桩
//        PowerMockito.when(underTest, nameOfMethodToMock, input).thenReturn(expected);
        PowerMockito.doReturn(expected).when(underTest).methodToTest();

        // 执行
        String toTest = underTest.methodToTest();

        // 验证
        // doReturn 设置不会执行when()后的method()方法,依旧会获得和期望值一样的结果
        assertEquals(expected, toTest);
    }
}

public class PrivatePartialMockingExample {
    public String methodToTest() {
        System.out.println("public Method");
        return methodToMock("input");
    }

    private String methodToMock(String input) {
        System.out.println("private Method");
        return "REAL VALUE = " + input;
    }
}

4.Mock的细节

@PrepareForTest(IdGenerator.class)

告诉PowerMock为测试准备某些类,放在测试类和单独的测试方法。支持通配符 @PrepareForTest("com.mypackage.*")

mock和spy

  • 使用Mock生成的类,所有方法都不是真实的方法,而且返回值都是NULL。

  • 使用Spy生成的类,所有方法都是真实方法,返回值都是和真实方法一样的。

when和doReturn

when(dao.getOrder()).thenReturn("returened by mock ");

doReturn(expected).when(underTest).methodToTest();

使用when去设置模拟返回值时,它里面的方法(dao.getOrder())会先执行一次。

使用doReturn去设置的话,就不会产生上面的问题,因为有when来进行控制要模拟的方法,所以不会执行原来的方法。

doNothing()和verify()

doNothing() 用于模拟void方法,其实不做任何事。

Mockito.verify() 验证某些方法使用了几次(默认1次),否定则抛出异常

@Test
public void getTotalEmployee() {
    EmployeeService service = PowerMockito.mock(EmployeeService.class);

    //doNothing() 用于执行void方法,不做任何事
    PowerMockito.doNothing().when(service).getTotalEmployee();

    service.getTotalEmployee();

    //验证某些方法使用了几次(默认1次),否定则抛出异常
    Mockito.verify(service,Mockito.times(1)).getTotalEmployee();
}

whenNew和withArguments

whenNew 模拟new行为,并不会真的创建对象,withArguments 传入构造函数的参数,无参使用 withNoArguments()

@RunWith(PowerMockRunner.class)
@PrepareForTest(DirectoryStructure.class)
public class DirectoryStructureTest {
    @Test
    public void createDirectoryStructureWhenPathDoesntExist() throws Exception {
        final String directoryPath = "mocked path";

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

        // 这就是如何告诉PowerMockito模拟新文件的构造。
        PowerMockito.whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);

        //验证某个行为发生过几次(默认1次)
        PowerMockito.verifyNew(File.class,times(0)).withArguments(directoryPath);

    }
}

Arguments Matcher

一个作为参数的接口类,可以应对“根据不同参数返回不同值”的场景。

       PowerMockito.when(demoService.findNameById(Mockito.argThat(new ArgumentMatcher<String>() {
            @Override
            public boolean matches(String s) {
                if (s.equals("Jerry")) {
                    return true;
                } else {
                    return false;
                }
            }
        }))).thenReturn("Marry");
        System.out.println(demoService.findNameById("Jerry")); //返回Marry
        System.out.println(demoService.findNameById("NotJerry")); //返回null

Answer interface

一个作为参数的接口类,可以应对“根据不同参数返回不同值”的场景。更强大。

        PowerMockito.when(demoService.findNameById(Mockito.anyString())).then(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) {
                Object[] arguments = invocationOnMock.getArguments();
                String arg = (String) arguments[0];
                if (arg.equals("Jerry")){
                    return "Marry";
                }else {
                    return "Not";
                }
            }
        });
        System.out.println(demoService.findNameById("Jerry")); //返回Marry
        System.out.println(demoService.findNameById("NotJerry")); //返回Not

任意数

ArgumentMatchers.anyLong()

示例:

PowerMockito.doReturn(application).when(Application).getById(ArgumentMatchers.anyLong());

注意:不可以出现

PowerMockito.doReturn(application).when(Application).getById(ArgumentMatchers.anyLong(), object);

上面的这种形式,要么全用虚拟参数,要么不用。而且,ArgumentMatchers只适用于此处,不能在其它地方使用。

5.MockMVC、PowerMock集成Spring

首先看我的依赖,对于mockito-core的2.8.55版本我一直无法进行Maven下载,无奈手动下载导包。

    <properties>
        <java.version>1.8</java.version>
        <powermock.version>1.7.1</powermock.version>
        <mock.version>2.8.55</mock.version>
    </properties>

    <dependencies>
        <!-- mockito-core 手动安装 -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>${mock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>${powermock.version}</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-testng</artifactId>
            <version>${powermock.version}</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-classloading-xstream</artifactId>
            <version>${powermock.version}</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4-rule</artifactId>
            <version>${powermock.version}</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-support</artifactId>
            <version>${powermock.version}</version>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

假如我的Controller层有这么一个方法

@RestController
public class DemoController {
    @Autowired
    DemoService demoService;

    @RequestMapping("/demo")
    public User mapping(){
        return demoService.create();
    }

}

他的DemoService方法是下面这个简单的例子

@Service
public class DemoService {

    public User create(){
        return new User("service",1);
    }

}

我只想测试这个接口,那么我可以这么写测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MockdemoApplication.class)
public class MockdemoApplicationTests{

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void contextLoads() throws Exception {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/demo")).andReturn();
        String content = result.getResponse().getContentAsString();
        System.out.println(content);
    }

}

关于MockMvc需要我们手动注入,我写了一个Bean专门做注入,这个Bean实现了ApplicationListener接口,原因后面会讲到。

@Configuration
public class AutoPowerMock implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Bean
    public MockMvc getMockMvc() {
        return MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

  }

这样执行我的测试用例,是可以完全成功的!

但需要考虑的是,如果在应用中需要消费其他服务的API。由于我依赖的服务并不由我所在的项目组维护(对方可能接口中途会发生变化,甚至,有时候可能并未启动)。集成测试成本略高,故而需要Mock测试。

对于任意一个类的Mock,开始我们已经讲过,那对于Spring的Bean的Mock,就需要多费点心思。

考虑到@Autowired注解会把Bean注入到测试类中,而且在程序运行时,调用的也都是Spring容器的Bean,所以一个简单的思路就是:把Spring容器中的Bean替换成我们Mock后的Bean

这就用到上面的对ApplicationListener的实现类了,它的onApplicationEvent方法会在Bean加载完后调用,在调用时我们手动对Bean进行偷天换日,完整代码如下

@Configuration
public class AutoPowerMock implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Bean
    public MockMvc getMockMvc() {
        return MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //这是需要Mock掉的Bean
        Class<?> value = DemoService.class;
        //获取BeanFactory
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) webApplicationContext.getAutowireCapableBeanFactory();
        //Mock
        Object mock = PowerMockito.mock(value);
        //替换
        String[] beanNames = beanFactory.getBeanNamesForType(value);
        for (String bean : beanNames) {
            beanFactory.removeBeanDefinition(bean);
            beanFactory.registerSingleton(bean, mock);
        }
    }

}

我收藏的学习资源

IBM的PowerMock教程

Spring Boot、Dubbo项目Mock测试踩坑与总结

SpringMVC测试mockMVC

本文已授权公众号后端技术精选发布

PowerMock单元测试踩坑与总结的更多相关文章

  1. vue2项目,踩坑Jest单元测试

    目前的项目已经维护了挺久,由于客户要求,我们要为项目加上单元测试,挑选一番后选择了Jest(配置简便,开箱即用),下面记录了此次为项目添加Jest作为单元测试的经历. 安装Jest 1. 在项目目录下 ...

  2. 【踩坑速记】二次依赖?android studio编译运行各种踩坑解决方案,杜绝弯路,总有你想要的~

    这篇博客,只是把自己在开发中经常遇到的打包编译问题以及解决方案给大家稍微分享一下,不求吸睛,但求有用. 1.大家都知道我们常常会遇到dex超出方法数的问题,所以很多人都会采用android.suppo ...

  3. HashMap踩坑实录——谁动了我的奶酪

    说到HashMap,hashCode 和 equals ,想必绝大多数人都不会陌生,然而你真的了解这它们的机制么?本文将通过一个简单的Demo还原我自己前不久在 HashMap 上导致的线上问题,看看 ...

  4. Spring5.x源码分析 | 从踩坑到放弃之环境搭建

    Spring5.x源码分析--从踩坑到放弃之环境搭建 前言 自从Spring发行4.x后,很久没去好好看过Spring源码了,加上最近半年工作都是偏管理和参与设计为主,在技术细节上或多或少有点疏忽,最 ...

  5. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  6. Spark踩坑记——数据库(Hbase+Mysql)

    [TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...

  7. NPOI导出Excel (C#) 踩坑 之--The maximum column width for an individual cell is 255 charaters

    /******************************************************************* * 版权所有: * 类 名 称:ExcelHelper * 作 ...

  8. 我的微信小程序入门踩坑之旅

    前言 更好的阅读体验请:我的微信小程序入门踩坑之旅 小程序出来也有一段日子了,刚出来时也留意了一下.不过赶上生病,加上公司里也有别的事,主要是自己犯懒,就一直没做.这星期一,赶紧趁着这股热乎劲,也不是 ...

  9. router路由去掉#!的踩坑记

    项目中在研究去掉router#!的过程中的踩坑过程.

随机推荐

  1. SpringBoot集成Swagger接口管理工具

    手写Api文档的几个痛点: 文档需要更新的时候,需要再次发送一份给前端,也就是文档更新交流不及时. 接口返回结果不明确 不能直接在线测试接口,通常需要使用工具,比如postman 接口文档太多,不好管 ...

  2. Pycharm基本设置和插件安装

    Pycharm调节主题和字体 调节主题:File - Setting - Editor - Color Scheme - 选择个人喜欢的风格 调节字体大小,感觉默认字体有点小,对我这样的老人家,至少要 ...

  3. virtualbbox centos7 NAT模式外网 Host-only Adapter模式联网 双网卡

    1.下载oracle VM virtualbox  centos7 1.1. 下载地址:https://www.virtualbox.org/wiki/Downloads https://www.ce ...

  4. Linux下图形数据库Neo4j单机安装

    Neo4j数据库简介 Neo4j 是一个NoSQL的图形数据库(Graph Database).Neo4j使用图(graph)相关的概念来描述数据模型,把数据保存为图中的节点以及节点之间的关系.很多应 ...

  5. socket网络编程之不间断通信

    socket是python提供的一种网络通信方式. socket是应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议 ...

  6. 转:敏捷开发之Scrum扫盲篇

    现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP... 为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述Scrum中 ...

  7. window中常用的命令

    1.Ctrl+s 保存 2.Ctrl+c 复制 3.Ctrl+v 粘贴 4.Ctrl+x 剪切 5.Ctrl+a 全选 6.Ctrl+f 查找 7.Windows+d 桌面 8.Windows+e 计 ...

  8. PostgreSQL条件表达式 case when then end

    例: SELECT CASE WHEN (store_size <= (100)::NUMERIC) THEN '小店'::TEXT WHEN (store_size >= (200):: ...

  9. C#深度学习のTask(基于任务的异步模型)

    一.Task关键字解释 Task 类的表示的单个操作不会返回一个值,通常以异步方式执行. Task 对象是一种的中心思想 基于任务的异步编程模式 首次引入.NET Framework 4 中. 因为由 ...

  10. centos7+nginx负载均衡Tomcat服务

    接着上一篇:www.cnblogs.com/lkun/p/8252815.html 我们在上一篇在一台centos7服务器上部署了两个nginx,接下来我们使用一个nginx实现tomcat的负载均衡 ...