iT邦幫忙

2024 iThome 鐵人賽

DAY 24
0

在 Spring Boot 中,單元測試( Unit Test )是一個很重要的環節,它是一個測試程式碼最小功能單位且在不涉及其他依賴關係的情況下進行正確性驗證的程序。在物件導向程式設計中,最小的單元就是方法

模擬對象( Mock ),是透過可控的方式模擬真實對象行為的假的對象。在物件導向程式設計中,通常會透過Mock來測試並模擬真實行為。 透過模擬對象的好處是,你的程式不會因為外部服務的改動而被影響,造成今天測試會過但明天測試不過的狀況。

Mockito 是一種 Java mock 框架,主要被用來進行模擬測試。它可以模擬 Spring 管理的 Bean、模擬方法的返回值、模擬拋出異常。我們可以透過模擬方法的參數、調用順序來驗證這個 Mock 是否有被正確的順序調用最後驗證結果是否符合預期。

要讓 Spring Boot 中的業務邏輯可以進行單元測試,首先需於 pom.xml 加入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • 想要使用 @Mock 註解可以透過以下方法:

    1. 在測試的Class上加入@RunWith(MockitJUnitRunner.class),JUnit5後的可以改用@ExtendWith(MockitoExtension.class)

    2. 寫一個測試執行前的Mock Initialization Function

      /**
      此方法為 Mockito 2 之前的版本之初始化方法
      在單元測試結束後不會主動釋放記憶體資源
      */
      @Before
      public void initMocks(){
              MokitoAnnotations.initMocks(this);
      }
      
      /**
      此方法為 Mockito 3.2.0 之後的版本之初始化方法
      在單元測試結束後,會透過AutoCloseable的方法清理資源、釋放記憶體,避免資源洩漏
      */
      @Before
      public void initMocks(){
              MockitoAnnotations.openMocks(this);
      }
      
  • 透過@InjectMock 註解標註受測Service

    當要測試Service且內部有太多依賴時,可以使用@InjectMock 註解進行標註,並將依賴的類別標記為@Mock (可以把@InjectMock當作業務邏輯層對象,@Mock當作資料存取層對象)

    @Mock
    private ApiService apiService;
    @InjectMocks
    private StudentService studentService;
    
  • 使用@spy 註解標註受測Service

    @Mock@spy註解都可以將需要受測的Service進行Mock,但@Mock會讓受測Service內的所有方法都進行Mock,而@spy 可以讓類別內的某些方法模擬,其它方法都會被實際調用。

    使用 @spy 註解標註受測的Service範例如下:

    @Service("StudentService")
    public class StudentService {
    
        private StudentInformationRepository studentInformationRepository;
        private StudentScoresRepository studentScoresRepository;
    
        int count = 0;
    
        @Autowired
        public StudentService(StudentInformationRepository studentInformationRepository,
                              StudentScoresRepository studentScoresRepository) {
            this.studentInformationRepository = studentInformationRepository;
            this.studentScoresRepository = studentScoresRepository;
        }
    
        public int getClassNumberOfPeople(String className){
    
            /**
             業務邏輯...
             */
    
            return count;
        }
    
        public float calculAverageScore(List<StudentScores> studentScoreList){
    
            /**
             業務邏輯...
             */
    
            return (float) totalScore / studentScoreList.size();
        }
    
    }
    
    @Spy
    StudentService studentService ;
    
    @Test
    void calculAverageScoreTest(){
    		doReturn(85).when(studentService)
            .calculAverageScore(Mockito.anyString());
    
        int numberOfPeople = studentService.getClassNumberOfPeople("A");
    }
    

    如果將受測的 StudentService 標註為@spy,因studentService.calculAverageScore()方法已經被定義了一個Mock,而studentService.getClassNumberOfPeople()方法並沒有被定義Mock,故StudentService 就會看起來像是只有部分被Mock。


上一篇
Day23 - 初窺鎖
下一篇
Day25 - JUnit單元測試(下)
系列文
這些年SpringBoot實戰開發教會我的事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言