文章目錄
- 問題描述
- 問題分析
- 解決
- Thread.sleep
- get()
- Mockito.lenient()
問題描述
有個接口使用CompletableFuture實現的異步調用,現在要用Mockito寫單元測試
@Testpublic void updateNumAsync() {Integer newNum = 600;// updateRoleCountAsync用CompletableFuture異步調用的ApiUtil.put發送http請求更新對方服務端的數據// 生成要用的stubwhen(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {assertEquals(result.getCode(), 0);});}
結果測試不通過:
Tests Failed: 1 of 1 test
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
問題分析
看控制臺輸出的意思大概就是when(...).return(...)
mock的stub沒被用到,然后測試不通過。
因為測試過程主要就是:1)mock一個要用的stub; 2)調用待測接口;3)檢查結果。由于這里是異步調用,updateNumAsync
里調用的CompletableFuture.supplyAsync()
用的ForkJoinPool,會有一個線程1在后臺異步執行updateNum的操作,因此猜測可能是當前test的線程0在異步過程中先結束了,導致線程0 Mock的stub并沒有被線程1執行的待測試接口用到,導致Tests Failed
。
解決
Thread.sleep
既然Test的線程0結束的太早,那么強行讓他多等一會是不是就好了?
@Testpublic void updateNumAsync() throws InterruptedException {Integer newNum = 600;// updateRoleCountAsync用CompletableFuture異步調用的ApiUtil.put發送http請求更新對方服務端的數據// 生成要用的stubwhen(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {assertEquals(result.getCode(), 0);});Thread.sleep(1000L);}
結果測試通過,證明之前的猜想應該是對的。但不太推薦這樣做。
Tests Passed: 1 of 1 test
get()
CompletableFuture
通過get()
獲取異步調用結果時,會阻塞當前線程直到異步操作結束返回。也就是說test的線程0不會提早結束,導致虛擬機棧中的stub在被線程1 調用之前被回收。
@Testpublic void updateNumAsync() throws InterruptedException, ExecutionException {Integer newNum = 600;// updateRoleCountAsync用CompletableFuture異步調用的ApiUtil.put發送http請求更新對方服務端的數據// 生成要用的stubwhen(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {assertEquals(result.getCode(), 0);}).get();}
結果測試通過.
Tests Passed: 1 of 1 test
Mockito.lenient()
stackoverflow上面有人在mock多個stub的同時(用了get()
),但也還會出現Unnecessary stubbings detected.
,詳情可以看原帖。大概就是有時Mockito可能沒有按照確定的順序調用這些方法,此時就可以用lenient()
。
這個方法在前面那個問題里也是能讓測試通過的。
@Testpublic void updateNumAsync(){Integer newNum = 600;// updateRoleCountAsync用CompletableFuture異步調用的ApiUtil.put發送http請求更新對方服務端的數據// 生成要用的stubMockito.lenient().when(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {assertEquals(result.getCode(), 0);});}
具體原理還不是很明白,反正就是能work,先埋個坑,之后有空再看看⑧