这篇讲解Spock自带的mock功能如何和power mock组合使用,发挥更强大的做用html
在上一篇的例子中使用power mock让静态方法返回一个指定的值,那能不能每次返回不一样的值呢?java
咱们先看下什么场景须要这样作:app
/** * 静态方法多分支场景 * @param userVO * @return */ public List<OrderVO> getUserOrdersBySource(UserVO userVO){ List<OrderVO> 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
code
这样的写法就能够每次测试业务方法时,让HttpContextUtils.getCurrentSource()
和HttpContextUtils.getCurrentCurrency()
返回不一样的来源和币种,就能轻松的覆盖if和else的分支代码htm
即Spock使用where表格的方式让power mock具备了动态mock的功能对象
上个例子讲了把power mock返回的mock值做为变量放在where
里使用,以达到动态mock静态方法的功能,这里再介绍一种动态mock 静态+final变量的用法,仍是先看业务代码,了解这么作的背景:接口
/** * 静态final变量场景 * @param orders * @return */ public List<OrderVO> convertUserOrders(List<OrderDTO> orders){ List<OrderVO> 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端订单" }
def orderMapper = Mock(OrderMapper.class)
Whitebox.setInternalState()
对OrderMapper接口的static final常量INSTANCE赋值(Spock不支持静态常量的mock),赋的值正是使用spock mock的对象orderMapperorderMapper.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 mock结合power mock测试静态常量,达到覆盖if else不一样分支逻辑的功能
因而可知Spock能够和power mock深度结合,测试一些特殊的场景,也能够按照这个思路继续挖掘其余用法