使用 Mockito 輔助單元測試

瞭解過單元測試相關概念的人應該會清楚一個概念:一個好的單元測試應該是與環境無關的,每個測試都是相互獨立的。亦即你能夠在任何地方,以任意順序運行這些測試,最後獲得的結果是同樣的。可是我被測試的類/方法中自己夾雜着對其它類的依賴,這又該怎麼處理呢,將依賴進行 mock 是其中一個作法。本文將記錄我在測試過程當中的一些備忘,以及遇到的一些問題。java

背景說明

我要對我正在開發的一個考試系統中的題目管理部分進行單元測試,這部分主要有一個 SubjectService 接口及其對應的實現類 SubjectServiceImpl,Service 內部又依賴於 DAO 層的兩個 Mapper(SubjectMapperSubjectAnswerMapper)。如今我要對 Service 層進行單元測試。此爲背景。git

過程

首先要肯定一個概念:測 Service 層,咱們要測它的什麼?Service 層對數據庫的訪問是經過 DAO 層進行的。那麼對數據庫相關的操做就不適宜放在這裏進行測試(對它們的測試應該放在 DAO 層)。Service 層做爲主要業務邏輯的載體,對 Service 層的測試應該圍繞流程進行(對於不合法的輸入,應該拋出對應的異常;對於正常的輸入,則流程應該能正常走完,至於數據庫訪問的正確與否,交給 DAO 層的單元測試進行保證)。github

肯定了這一點以後,接下來就能夠開始測試流程了。首先是引入相關的測試框架。因爲項目採用了 SpringBoot,我參考了參考資料中的內容,構建起整個測試環境的依賴。spring

而後就是開始編寫相關的測試類:數據庫

@RunWith(MockitoJUnitRunner.class)
public class SubjectServiceImplTest { 
    private SubjectServiceImpl subjectServiceImpl;
}

對於 Service 所依賴的兩個 DAO,只須要建立對應的兩個 Mapper 併爲其加上 @Mock 註解,而後在被測試對象上加上 @InjectMocks 註解,即完成了對依賴的 mock:springboot

@RunWith(MockitoJUnitRunner.class)
public class SubjectServiceImplTest {
    @Mock
    private SubjectMapper subjectMapper;
    
    @Mock
    private SubjectAnswerMapper subjectAnswerMapper;
    
    @InjectMocks
    private SubjectServiceImpl subjectServiceImpl;
}

而後就能夠開始測試咱們的 Service 的方法了。因爲 Mock 的引入,如今測試方法的整個流程變成了 4 個步驟:app

  1. 準備測試用的輸入
  2. 給 Mock 對象設置預期的輸出(由於被測對象所依賴的是由你虛擬出來的東西,因此依賴應該怎麼響應須要你手動設置)
  3. 運行被測方法
  4. 檢查運行結果是否與預期一致

如下是一個例子框架

/**
     * 測試插入沒有答案的試題
     * 應該拋出異常
     */
@Test
public void testSaveSubjectWithoutAnswer() {
    SubjectDTO subjectDTO = new SubjectDTO();
    subjectDTO.setName("testSubject");
    subjectDTO.setDifficulty(1L);
    subjectDTO.setCategoryId(1L);
    subjectDTO.setSubjectTypeId(1L);

    Mockito.when(subjectMapper.insert(Mockito.any()))
        .thenReturn(1);

    try {
        subjectService.saveSubject(subjectDTO);
    } catch (BusinessException e) {
        assertEquals(e.getCode(), ResultEnum.INCOMPLETE_ADD_EXERCISE_INFORMATION.getCode());
        return;
    }
    throw new RuntimeException("Should not reach here!");
}

在上面的例子中,步驟 2 使用到了 Mockito 類的一些靜態方法,設定了 Mapper 裏會被調用方法的響應。(這裏建議爲了簡化代碼,能夠經過 import static 的方式引入 Mockito 的全部方法,這樣能夠省略前面的類名)受限於我使用的 JUnit 爲 JUnit 4,因此對異常的測試只能這樣進行,在 JUnit 5 中就添加了對預期拋出異常的 assert。單元測試

會作這個測試,其他的測試也就基本可以進行下去了。測試

遇到的問題

在跑的過程當中,我發現了一個挺棘手的問題,目前還沒找到合適的方案。

項目的 DAO 層使用的是 MyBatis + 通用 Mapper 這一套框架。我在 Mock 方法的時候發如今運行的過程當中,有關 Mapper 方法中的 selectByExample 的部分老是運行不了,我在方法內部寫了建立 Example 的過程,若是使用 Mock,建立 Example 的過程會出現異常,內容大概是要依賴一個數據庫環境。因此在不考慮 Service 和 DAO 集成測試的狀況下,涉及到這部分的 Service 的部分沒法進行測試,後續我會繼續查閱相關資料並更新此文。

參考資料

SpringBoot2.x 單元測試 | 閃爍之狐

相關文章
相關標籤/搜索