如何使用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。

推薦閱讀: