mock when 无返回怎么办_Spock高级用法 动态mock

本文是Spock系列的第八篇,探讨如何利用Spock的where标签与Power Mock配合,实现动态模拟静态方法的返回值。通过示例展示了在测试中如何使静态方法如HttpContextUtils.getCurrentSource()和getCurrentCurrency()返回不同的值,覆盖if-else分支逻辑。同时,文章还介绍了如何动态mock接口的静态final变量,如OrderMapper.INSTANCE,以控制其方法convert()的返回值,从而更好地进行单元测试。作者是一位有多年经验的Java开发者,分享了实用的测试技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是Spock系列的第八篇文章,上一篇介绍了Spock如何使用power mock测试静态方法,这篇讲解Spock自带的mock功能如何和power mock深度结合,发挥更强大的作用

动态mock静态方法 (spock where + power mock)

在上一篇的例子中使用power mock让静态方法返回一个指定的值,那能不能每次返回不同的值呢?

我们先看下什么场景需要这样做:

/**
 * 静态方法多分支场景
 * @param userVO
 * @return
 */
public ListgetUserOrdersBySource(UserVO userVO){
    List orderList = new ArrayList<>();
    OrderVO order = new OrderVO();if ("APP".equals(HttpContextUtils.getCurrentSource())) { // 手机来源if("CNY".equals(HttpContextUtils.getCurrentCurrency())){ // 人民币// TODO 针对App端的订单,并且请求币种为人民币的业务逻辑...
            System.out.println("source -> APP, currency -> CNY");
        } else {
            System.out.println("source -> APP, currency -> !CNY");
        }
        order.setType(1);
    } else if ("WAP".equals(HttpContextUtils.getCurrentSource())) { // H5来源// TODO 针对H5端的业务逻辑...
        System.out.println("source -> WAP");
        order.setType(2);
    } else if ("ONLINE".equals(HttpContextUtils.getCurrentSource())) { // PC来源// TODO 针对PC端的业务逻辑...
        System.out.println("source -> ONLINE");
        order.setType(3);
    }
    orderList.add(order);return orderList;
}

这段代码的 if else 分支逻辑主要是依据HttpContextUtils这个工具类的静态方法 getCurrentSource() 和 getCurrentCurrency() 的返回值决定流程的

这样的业务代码也是我们平时写单测经常遇到的场景,如果能让HttpContextUtils.getCurrentSource() 静态方法每次mock出不同的值,就可以很方便的覆盖if else的全部分支逻辑

Spock的where标签可以方便的和power mock结合使用,让power mock模拟的静态方法每次返回不同的值,代码如下:

/**
 * 测试静态方法mock
 * @Author: www.javakk.com
 * @Description: 公众号:Java老K
 */
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([HttpContextUtils.class])
class OrderServiceStaticTest extends Specification {

    def orderService = new OrderService()

    void setup() {
        // mock静态类
        PowerMockito.mockStatic(HttpContextUtils.class)
    }

    /**
     * 测试spock的mock和power mock静态方法组合用法的场景
     */
    @Unroll
    def "当来源是#source时,订单类型为:#type"() {
        given: "mock当前上下文的请求来源"
        PowerMockito.when(HttpContextUtils.getCurrentSource()).thenReturn(source)

        and: "mock当前上下文的币种"
        PowerMockito.when(HttpContextUtils.getCurrentCurrency()).thenReturn(currency)

        when: "调用获取用户订单列表"
        def orderList = orderService.getUserOrdersBySource(new UserVO())

        then: "验证返回结果是否符合预期值"
        with(orderList) {
            it[0].type == type
        }

        where: "表格方式验证订单信息的分支场景"
        source   | currency || type
        "APP"    | "CNY"    || 1
        "APP"    | "USD"    || 1
        "WAP"    | ""       || 2
        "ONLINE" | ""       || 3
    }
}

powermock的thenReturn方法返回的值是 source 和 currency 两个变量,不是具体的数据,这两个变量对应where标签里的前两列 "source | currency" 

这样的写法就可以每次测试业务方法时,让HttpContextUtils.getCurrentSource()和HttpContextUtils.getCurrentCurrency() 返回不同的来源和币种,就能轻松的覆盖if和else的分支代码

Spock使用where表格的方式让power mock具有了动态mock的功能

动态mock接口 (spock mock + power mock + where)

上个例子讲了把power mock返回的mock值作为变量放在where里使用,以达到动态mock静态方法的功能,这里再介绍一种动态mock 静态+final变量的用法,还是先看业务代码,了解这么做的背景:

/**
 * 静态final变量场景
 * @param orders
 * @return
 */
public ListconvertUserOrders(List orders){
    List orderList = new ArrayList<>();for (OrderDTO orderDTO : orders) {
        OrderVO orderVO = OrderMapper.INSTANCE.convert(orderDTO); // VO DTO 属性转换if (1 == orderVO.getType()) {
            orderVO.setOrderDesc("App端订单");
        } else if(2 == orderVO.getType()) {
            orderVO.setOrderDesc("H5端订单");
        } else if(3 == orderVO.getType()) {
            orderVO.setOrderDesc("PC端订单");
        }
        orderList.add(orderVO);
    }return orderList;
}

这段代码里的for循环第一行调用了"OrderMapper.INSTANCE.convert()"转换方法,将orderDTO转换为orderVO,然后根据type的值走不同的分支

而OrderMapper是一个接口,代码如下:

import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * 订单属性转换
 */
@Mapper
public interface OrderMapper {

    // 即使不用static final修饰,接口里的变量默认也是静态、final的
    static final OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

    @Mappings({})
    OrderVO convert(OrderDTO requestDTO);
}

"INSTANCE"是接口OrderMapper里定义的变量,接口里的变量默认都是static final的,所以我们要先把这个INSTANCE静态final变量mock掉,这样才能调用它的方法convert() 返回我们想要的值

OrderMapper这个接口是mapstruct工具的用法,mapstruct是做对象属性映射的一个工具,它会自动生成OrderMapper接口的实现类,生成对应的set、get方法,把orderDTO的属性值赋给orderVO属性,比使用反射的方式好很多(具体用法自行百度)

看下Spock如何写这个单元测试:
/**
 * 测试spock的mock和powermock静态final变量结合的用法
 */
@Unroll
def "ConvertUserOrders"() {
    given: "mock掉OrderMapper的静态final变量INSTANCE,并结合spock设置动态返回值"
    def orderMapper = Mock(OrderMapper.class)
    Whitebox.setInternalState(OrderMapper.class, "INSTANCE", orderMapper)
    orderMapper.convert(_) >> order

    when: "调用用户订单转换方法"
    def userOrders = orderService.convertUserOrders([new OrderDTO()])

    then: "验证返回结果是否符合预期值"
    with(userOrders) {
        it[0].orderDesc == desc
    }

    where: "表格方式验证订单属性转换结果"
    order                || desc
    new OrderVO(type: 1) || "App端订单"
    new OrderVO(type: 2) || "H5端订单"
    new OrderVO(type: 3) || "PC端订单"
}
1. 首先使用Spock自带的Mock()方法,将OrderMapper类mock为一个模拟对象orderMapper,"def orderMapper = Mock(OrderMapper.class)"2. 然后使用power mock的Whitebox.setInternalState()对OrderMapper接口的static final常量INSTANCE赋值(Spock不支持静态常量的mock),赋的值正是使用spock mock的对象orderMapper3. 使用Spock的mock模拟convert()方法调用,"orderMapper.convert(_) >> order",再结合where表格,实现动态mock接口的功能主要就是这3行代码:
def orderMapper = Mock(OrderMapper.class) // 先使用Spock的mock
Whitebox.setInternalState(OrderMapper.class, "INSTANCE", orderMapper) // 将第一步mock的对象orderMapper 使用power mock赋值给静态常量INSTANCEmock
orderMapper.convert(_) >> order // 结合where模拟不同的返回值
(完整的源码在公众号里回复spock获取)这样就可以使用Spock mock结合power mock测试静态常量,达到覆盖if else不同分支逻辑的功能由此可见Spock可以和power mock深度结合测试一些特殊的场景,也可以按照这个思路继续挖掘其他用法- END -

互联网一线java开发老兵,工作10年有余,梦想敲一辈子代码,以梦为码,不负韶华。

70e47eaef21632ff36b2072a936769ba.png

扫码关注Java老K,获取更多Java干货。

mock一个无返回值的方法,可以使用Mockito的doNothing()方法。下面是一个示例代码: ```java MyClass myObject = Mockito.mock(MyClass.class); Mockito.doNothing().when(myObject).voidMethod(); ``` 在这个示例中,我们创建了一个名为`myObject`的mock对象,并使用`doNothing()`方法来mock它的`voidMethod()`方法,表示当调用`voidMethod()`时不做任何操作。 另外,如果你想对无返回值的方法进行验证,可以使用`verify()`方法。例如: ```java Mockito.verify(myObject, Mockito.times(1)).voidMethod(); ``` 这段代码将验证`voidMethod()`方法是否被调用了一次。 引用: \[2\] 可以用Mockito的doNothing()、doThrow()和doAnswer()来对无返回值的函数进行Mock和验证。\[2\] #### 引用[.reference_title] - *1* [利用Mockito进行mock方法时有无返回值的处理](https://blog.youkuaiyun.com/weixin_43221845/article/details/84847160)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [用Mockito来mock返回值类型为void的方法](https://blog.youkuaiyun.com/w605283073/article/details/89196668)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Spock单元测试框架介绍及在美团优选的实践_第三章(void无返回值方法mock方式)](https://blog.youkuaiyun.com/zhx__/article/details/125621696)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值