這篇講解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深度結合,測試一些特殊的場景,也能夠按照這個思路繼續挖掘其餘用法