如何使用Mockito調用靜態方法和void方法
一、mock 靜態方法
mockito庫並不能mock靜態方法,需要依賴powermock
第一步:給類添加註解
// 靜態類優先加載,所以需要提前告訴powermock哪些靜態類需要mock @ContextConfiguration @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class) @PrepareForTest(靜態調用類.class) public class SupplierServiceImplTest extends PowerMockTestCase {}
第二步:mock使用
@Test(expectedExceptions = BusinessException.class) public void testAddSupplierAccount_genIdentityNoError() { // 告訴powermock,需要mock該類的所有靜態方法 PowerMockito.mockStatic(PasswordGenerator.class); final SupplierAccountDto supplierAccountDto = new SupplierAccountDto(); supplierAccountDto.setName("小明"); final String randomPWd = "666"; PowerMockito.when(supplierDao.selectByEmail(anyString())) .thenReturn(new ArrayList<HaitaoSupplier>()); // 靜態方法mock PowerMockito.when(PasswordGenerator.genPwd()).thenReturn(randomPWd); PowerMockito.when(pwEncoder.encode(anyString())).thenReturn(randomPWd); PowerMockito.when(identityNoGenerator.genIdentityNo()).thenReturn(-1L); supplierServiceImpl.addSupplierAccount(supplierAccountDto); verify(pwEncoder).encode(randomPWd); }
二、mock void 方法
// void嘛,doNothing顧名思義 PowerMockito.doNothing().when(casService).addSupplier(anyLong(), any(ServiceKey.class));
使用PowerMockito和Mockito進行模擬測試
包括靜態方法測試,私有方法測試等,以及方法執行的坑或者模擬不成功解決
一 普通spring項目
依賴:這個很重要,不同版本用法也有點區別:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>2.0.2-beta</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-core</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency>
接下來就是mock測試瞭,使用完全模擬測試過程,對於需要測試接口中調用的靜態,私有方法等,返回自己想要的預期結果,達到測試效果:
這裡有幾個要點:
測試過程中完全手動mock,不會真實調用或者產生數據
一 mock對象
order = mock(Order.class); user = mock(User.class);
二 屬性註入
將service等類中需要的其他service或者mapper等mock出來,然後分別使用工具類註入,名稱保持一致即可
roomTypeService = mock(RoomTypeServiceImpl.class); ticketComponetService = mock(TicketComponetServiceImpl.class); hotelMapper = mock(HotelMapper.class); //註入屬性 ReflectionTestUtils.setField(orderService, "hotelGroupMapper", hotelGroupMapper); ReflectionTestUtils.setField(orderService, "dsUtils", dsUtils); ReflectionTestUtils.setField(orderService, "orderMapper", orderMapper);
三 靜態方法mock
模擬靜態方法返回結果需要使用PowerMockit,測試類上必須加註解@PrepareForTest
//account 獲取stub PowerMockito.mockStatic(Account.class); Mockito.when(Account.get(anyString(), anyString(), anyString(), anyInt())).thenReturn(account);
四 私有方法
私有方法首先需要在類上加入註解,對於要測試的類中的public方法同樣有效,比如測試方法中包含一個public方法,可以同樣模擬:
@PrepareForTest(ConsumptionServiceImpl.class) //裡面寫需要模擬私有方法的類class
然後對象不能mock,必須new一個,並且需要用spy處理:
orderService = PowerMockito.spy(new OrderServiceImpl());
接著使用doreturn .when這種形式模式,不能使用先when後return這種,會報錯
註意一點,模擬參數要麼全部模擬,要麼全部自定義,不能混搭
這裡有個大坑,如果出現私有方法還是進去執行的情況,很大可能是參數不對,比如你mock的參數是 anyString(),那麼你真是測試時候傳遞的必須是一個String實例,不能是null,否則mock就會失敗,我這裡之前一直是對象的一個屬性,直接new瞭一個對象傳遞
所以一直不成功:
比如 方法需要的是user.getId() ,而且你mock的是一個anyInt(),那麼真正傳遞的時候必須給這個user,setId(9527),否則就無法達成預期的模擬效果,所有方法都一樣!!
try {<br> // 方法名,方法參數,必須全部對應,否則報錯方法找不到 PowerMockito.doReturn(1).when(orderService, "dateListMinBook",anyString(),anyString(),any(RoomType.class),anyString(),anyString()); PowerMockito.doReturn(ResponseMessage.success().pushData("dateRoomTypeList",new ArrayList<DateRoomType>())).when(orderService, "eachDateNumAndPrice",any(Order.class),any(RoomType.class),anyBoolean(),anyInt(),anyString(),any(User.class)); PowerMockito.doReturn("2000").when(orderService, "getKeeptimeByWxcidAndHotelidAndLevel",anyString(),anyString(),anyString()); PowerMockito.doNothing().when(orderService, "getPayWay",any(),any(),any(),any(),any()); } catch (Exception e) { e.printStackTrace(); }
五 預期結果
verify :判斷方法執行瞭幾次: 確定測試是否通過
例如:verify(userService, times(1)).queryUser(any(anyInt(),anyString(),anyString());
二 springboot項目使用
1 依賴
<!-- S-junit --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>2.0.2-beta</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.0-beta.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.0-beta.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-core</artifactId> <version>2.0.0-RC.4</version> <scope>test</scope> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.9</version> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>2.15.0</version> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.12.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>4.0.2</version> <scope>test</scope> </dependency> <!-- E-junit -->
2 創建測試基類
/** * 測試基類,所有子測試類繼承此類即可 */ @PowerMockRunnerDelegate(SpringRunner.class) @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.*", "javax.security.*"}) //忽略一些mock異常 @SpringBootTest public class TestBase { }
3 創建特定的測試類
public class HotelControllerTest extends TestBase { //繼承基類即可 @Mock private HotelService hotelService; private Integer id; // 加載springContext進行mock測試,真實調用方法,不需要mock步驟 // @Autowired // private HotelController hotelController; // 純mock測試,不加載springContext,執行mock操作,必須mock步驟,不會真實調用 @InjectMocks private HotelController hotelController=new HotelController(); // 應用到所有門店測試 @Test public void detailTest(){ System.out.println("test start....."); // 1 構造參數 ininParams(1); // 2 mock步驟 mockStep(); // 3 執行操作 ResponseMessage result = hotelController.detail(id); System.out.println(new Gson().toJson(result)); assertEquals(0, (int) result.getCode()); } private void mockStep() { when(hotelService.detail(anyInt())).thenReturn(ResponseMessage.success()); } private void ininParams(Integer type) { switch(type){ case 1: id=17317; break; case 2: id=2; break; default: break; } } }
4 模擬私有方法和靜態方法
@PrepareForTest(OrderServiceImpl.class) // 需要調用私有或者靜態方法的類 public class OrderControllerTest extends TestBase { private OrderServiceImpl orderServiceImpl; //需要調用私有或者靜態方法時,不能使用@Mock,還需要@before初始化屬性 @Mock private OrderMapper orderMapper; @Mock private RestTemplateUtil restTemplateUtil; private Integer orderId; private String wxcid; @Before public void init(){ //處理私有方法模擬實例 orderServiceImpl = PowerMockito.spy(new OrderServiceImpl()); //使用spy模擬的需要手動註入屬性,因為什麼都沒有 ReflectionTestUtils.setField(orderController, "iOrderService", orderServiceImpl); ReflectionTestUtils.setField(orderServiceImpl, "orderMapper", orderMapper); ReflectionTestUtils.setField(orderServiceImpl, "restTemplateUtil", restTemplateUtil); } //純mock測試,不加載springContext,執行mock操作,必須mock步驟,不會真實調用 @InjectMocks private OrderController orderController=new OrderController(); @Test public void cancelTest(){ System.out.println("test start....."); // 1 構造參數 ininParams(); // 2 mock步驟 mockStep(); // 3 執行操作 ResponseMessage cancel = orderController.cancel(wxcid, orderId); assertEquals(0,(int)cancel.getCode()); } private void mockStep() { Order order = new Order(); order.setStatus(2); when(orderMapper.getOrderByOrderId(anyInt())).thenReturn(order); when(orderMapper.updateStatus(anyInt(),anyInt())).thenReturn(2); JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("code",0); when(restTemplateUtil.postToCri(anyString(),anyString(),any())).thenReturn(jsonObject); //處理私有方法,必須用這種寫法 try { PowerMockito.doNothing().when(orderServiceImpl, "returnTicketTouser", anyString(),any()); PowerMockito.doReturn(ErrorCode.SUCCESS).when(orderServiceImpl, "refoundAndGetCode", any(),any(),any(),any()); } catch (Exception e) { e.printStackTrace(); } } private void ininParams() { wxcid="57af462dff475fe4644de32f08406aa8"; orderId=25864; } }
註意:
如果是分模塊項目,springboot項目的啟動類隻能有一個,即需要把其他service,dao,common模塊的啟動類的啟動註解給註釋掉,否則測試啟動會報錯
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Java單元測試Powermockito和Mockito使用總結
- 怎樣使用PowerMockito 測試靜態方法
- Junit Mockito實現單元測試方法介紹
- Java單元測試Mockito的使用詳解
- 基於SpringBoot Mock單元測試詳解