在 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
註解可以透過以下方法:
在測試的Class上加入@RunWith(MockitJUnitRunner.class)
,JUnit5後的可以改用@ExtendWith(MockitoExtension.class)
寫一個測試執行前的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。