Spock单元测试框架实战指南九 - 模拟抽象类方法

我们平时写单元测试时经常会遇到调用抽象类或父类的方法,这些抽象方法可能是调用底层接口或数据库,需要mock掉,让抽象方法返回一个我们指定的值,以便测试当前代码逻辑的场景。

下面讲下Spock如何结合power mock实现动态mock抽象方法

一. 抽象方法或父类方法动态Mock

AbstractService 是个抽象类,我们需要把它的方法 parentMethod 模拟掉,返回我们预先设置的"期望值"

代码示例:

public abstract class AbstractService {
    String parentMethod(){
        // 发起接口调用或数据库操作
        return "parentMethod value";
    }
}

SubService是继承AbstractService 的子类,在doSomething方法体里会调用抽象类的 parentMethod 方法逻辑,然后根据抽象方法 parentMethod 的返回值走不同的逻辑:

public class SubService extends AbstractService {

    @Autowired
    MoneyDAO moneyDAO;

    public String doSomething() {
        String parent = super.parentMethod(); // 调用抽象类或父类方法
        if ("parent1".equals(parent)) {
            // 执行parent1分支逻辑
            return "sub1";
        }
        if ("parent2".equals(parent)) {
            // 执行parent2分支逻辑
            return "sub2";
        }
        if ("parent3".equals(parent)) {
            // 执行parent3分支逻辑
            return "sub3";
        }
        return "other";
    }
}

如果要mock掉抽象类AbstractService 中的 parentMethod 方法, 并且每次mock的值不一样, 可以使用spock + powermock来实现:

单元测试代码如下:

/**
 * 测试抽象类方法或父类方法
 * @Author: www.javakk.com
 * @Description: 公众号:Java老K
 * @Date: Created in 14:53 2020/10/05
 * @Modified By:
 */
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([SubService.class])
class AbstractServiceTest extends Specification {
    @Unroll
    def "测试抽象方法"() {
        given: "mock抽象类方法"
        def sub = PowerMockito.mock(SubService)
        PowerMockito.when(sub.parentMethod()).thenReturn(parentValue) // mock掉抽象类的parentMethod, 返回动态mock值:mockParentReturn
        PowerMockito.when(sub.doSomething()).thenCallRealMethod()

        expect: "调用doSomething方法"
        sub.doSomething() == result

        where: "验证分支场景"
        parentValue | result
        "parent1"   | "sub1"
        "parent2"   | "sub2"
        "parent3"   | "sub3"
        "parent4"   | "other"
    }
}

使用power mock模拟掉抽象类的方法,返回一个变量parentValue,然后再放在Spock的where标签里,即可实现动态mock的效果,即每次调用返回的mock值都不一样:parent1parent2parent3 ...

二. 抽象方法+实例方法的动态Mock

如果在SubService中还有引用其他实例对象的方法,比如下面的业务代码:

public class SubService extends AbstractService {

    @Autowired
    MoneyDAO moneyDAO; // 金额换算对象

    public String doSomethingAndDao() {
        String parent = super.parentMethod(); // 调用抽象类或父类方法
        BigDecimal money = moneyDAO.getExchangeByCountry(parent); // 获取对应国家的金额
        if ("parent1".equals(parent)) {
            return money + " CNY";
        }
        if ("parent2".equals(parent)) {
            return money + " USD";
        }
        if ("parent3".equals(parent)) {
            return money + " EUR";
        }
        return money.toString();
    }
}

如果即要mock掉抽象类AbstractService 中的 parentMethod 方法,又要mockmoneyDAO对象,可以使用 Whitebox.setInternalState 方式

单元测试代码如下:

/**
 * 测试抽象类方法或父类方法
 * @Author: www.javakk.com
 * @Description: 公众号:Java老K
 * @Date: Created in 14:53 2020/10/05
 * @Modified By:
 */
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([SubService.class])
class AbstractServiceTest extends Specification {

    @Unroll
    def "测试抽象方法和实例方法"() {
        given: "mock抽象类方法"
        def sub = PowerMockito.mock(SubService)
        // mock掉抽象类的parentMethod, 返回动态mock值:mockParentReturn
        PowerMockito.when(sub.parentMethod()).thenReturn(parentValue)
        PowerMockito.when(sub.doSomethingAndDao()).thenCallRealMethod()

        def moneyDAO = Mock(MoneyDAO)
        //将Spockmock的对象moneyDAO使用powermock赋值给SubService的引用moneyDAO
        Whitebox.setInternalState(sub, "moneyDAO", moneyDAO)
        moneyDAO.getExchangeByCountry(_) >> money // 这样就可以使用spock的动态mock

        expect: "调用doSomething方法"
        sub.doSomethingAndDao() == result

        where: "验证分支场景"
        parentValue | money || result
        "parent1"   | 100   || "100 CNY"
        "parent2"   | 200   || "200 USD"
        "parent3"   | 300   || "300 EUR"
        "parent4"   | 400   || "400"
    }
}

文章来源:http://javakk.com/545.html


Java老K
十年java老兵,现就职上海某一线互联网大厂,专注java技术,包括多线程并发,RXjava,JVM,Spring,Spri...

十年java老兵,现就职上海某一线互联网大厂,专注java技术,包括多线程并发,RXjava,JVM,Spring,Spri...

33 声望
10 粉丝
0 条评论
推荐阅读
如何使用Eclipse内存分析工具定位内存泄露
打开公司监控系统查看应用各项指标发现JVM中老年代在持续增长(从上次发布10月30号到11月10号的12天内一直在增长, 存在内存泄露迹象)从图中可以看出, 从10月30号发布到11月10号oom期间11天老年代一直在缓慢上涨...

Java老K2阅读 3.5k

封面图
刨根问底 Redis, 面试过程真好使
充满寒气的互联网如何在面试中脱颖而出,平时积累很重要,八股文更不能少!下面带来的这篇 Redis 问答希望能够在你的 offer 上增添一把🔥。

菜农曰17阅读 1k

封面图
PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 2.1k评论 3

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide5阅读 885

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬8阅读 1.1k

与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。

lpe2348阅读 1.9k

封面图
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 2.3k评论 2

封面图

十年java老兵,现就职上海某一线互联网大厂,专注java技术,包括多线程并发,RXjava,JVM,Spring,Spri...

33 声望
10 粉丝
宣传栏