Spring 單元測試中如何進(jìn)行 mock的實(shí)現(xiàn)
我們在使用 Spring 開發(fā)項(xiàng)目時,都會用到依賴注入。如果程序依賴了外部系統(tǒng)或者不可控組件,比如依賴數(shù)據(jù)庫、網(wǎng)絡(luò)通信、文件系統(tǒng)等,我們在編寫單元測試時,并不需要實(shí)際對外部系統(tǒng)進(jìn)行操作,這時就要將被測試代碼與外部系統(tǒng)進(jìn)行解耦,而這種解耦方法就叫作 “mock”。所謂 “mock” 就是用一個“假”的服務(wù)代替真正的服務(wù)。
那我們?nèi)绾蝸?mock 服務(wù)進(jìn)行單元測試呢?mock 的方式主要有兩種:手動 mock 和利用單元測試框架 mock。其中,利用框架 mock 主要是為了簡化代碼編寫。我們這里主要是介紹利用框架 mock,而手動 mock 只是簡單介紹。
手動 mock
手動 mock 其實(shí)就是重新創(chuàng)建一個類繼承被 mock 的服務(wù)類,并重寫里面的方法。在單元測試中,利用依賴注入的方式使用 mock 的服務(wù)類替換原來的服務(wù)類。具體代碼示列如下:
/**
* UserRepository
*
* @author star
*/
@Repository
public class UserRepository {
/**
* 模擬從數(shù)據(jù)庫中獲取用戶信息,實(shí)際開發(fā)中需要連接真實(shí)的數(shù)據(jù)庫
*/
public User getUser(String name) {
User user = new User();
user.setName("testing");
user.setEmail("testing@outlook.com");
return user;
}
}
/**
* MockUserRepository
*
* @author star
*/
public class MockUserRepository extends UserRepository {
/**
* 模擬從數(shù)據(jù)庫中獲取用戶信息
*/
@Override
public User getUser(String name) {
User user = new User();
user.setName("mock-test-name");
user.setEmail("mock-test-email");
return user;
}
}
// 進(jìn)行單元測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceManualTest {
@Autowired
private UserService userService;
@Test
public void testGetUser_Manual() {
// 將 MockUserRepository 注入到 UserService 中
userService.setUserRepository(new MockUserRepository());
User user = userService.getUser("mock-test-name");
Assert.assertEquals("mock-test-name", user.getName());
Assert.assertEquals("mock-test-email", user.getEmail());
}
}
從上面的代碼中,我們可以看到手動 mock 需要編寫大量的額外代碼,同時被測試類也需要提供依賴注入的入口(setter 方法等)。如果被 mock 的類修改了函數(shù)名稱或者功能,mock 類也要跟著修改,增加了維護(hù)成本。
為了提高效率,減少維護(hù)成本,我們推薦使用單元測是框架進(jìn)行 mock。
利用框架 mock
這里我們主要介紹 Mokito.mock()、@Mock、@MockBean 這三種方式的 mock。
Mocito.mock()
Mocito.mock() 方法允許我們創(chuàng)建類或接口的 mock 對象。然后,我們可以使用 mock 對象指定其方法的返回值,并驗(yàn)證其方法是否被調(diào)用。代碼示列如下:
@Test
public void testGetUser_MockMethod() {
// 模擬 UserRepository,測試時不直接操作數(shù)據(jù)庫
UserRepository mockUserRepository = Mockito.mock(UserRepository.class);
// 將 mockUserRepository 注入到 UserService 類中
userService.setUserRepository(mockUserRepository);
User mockUser = mockUser();
Mockito.when(mockUserRepository.getUser(mockUser.getName()))
.thenReturn(mockUser);
User user = userService.getUser(mockUser.getName());
Assert.assertEquals(mockUser.getName(), user.getName());
Assert.assertEquals(mockUser.getEmail(), user.getEmail());
// 驗(yàn)證 mockUserRepository.getUser() 方法是否執(zhí)行
Mockito.verify(mockUserRepository).getUser(mockUser.getName());
}
@Mock
@Mock 是 Mockito.mock() 方法的簡寫。同樣,我們應(yīng)該只在測試類中使用它。與 Mockito.mock() 方法不同的是,我們需要在測試期間啟用 Mockito 注解才能使用 @Mock 注解。
我們可以調(diào)用 MockitoAnnotations.initMocks(this) 靜態(tài)方法來啟用 Mockito 注解。為了避免測試之間的副作用,建議在每次測試執(zhí)行之前先進(jìn)行以下操作:
@Before
public void setup() {
// 啟用 Mockito 注解
MockitoAnnotations.initMocks(this);
}
我們還可以使用另一種方法來啟用 Mockito 注解。通過在 @RunWith() 指定 MockitoJUnitRunner 來運(yùn)行測試:
@RunWith(MockitoJUnitRunner.class)
public class UserServiceMockTest {
}
下面我們來看看如何使用 @Mock 進(jìn)行服務(wù) mock。代碼示列如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceMockTest {
@Mock
private UserRepository userRepository;
@Autowired
@InjectMocks
private UserService userService;
private User mockUser() {
User user = new User();
user.setName("mock-test-name");
user.setEmail("mock-test-email");
return user;
}
@Before
public void setup() {
// 啟用 Mockito 注解
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetUser_MockAnnotation() {
User mockUser = mockUser();
Mockito.when(userRepository.getUser(mockUser.getName()))
.thenReturn(mockUser);
User user = userService.getUser(mockUser.getName());
Assert.assertEquals(mockUser.getName(), user.getName());
Assert.assertEquals(mockUser.getEmail(), user.getEmail());
// 驗(yàn)證 mockUserRepository.getUser() 方法是否執(zhí)行
Mockito.verify(userRepository).getUser(mockUser.getName());
}
}
Mockito 的 @InjectMocks 注解作用是將 @Mock 所修飾的 mock 對象注入到指定類中替換原有的對象。
@MockBean
@MockBean 是 Spring Boot 中的注解。我們可以使用 @MockBean 將 mock 對象添加到 Spring 應(yīng)用程序上下文中。該 mock 對象將替換應(yīng)用程序上下文中任何現(xiàn)有的相同類型的 bean。如果應(yīng)用程序上下文中沒有相同類型的 bean,它將使用 mock 的對象作為 bean 添加到上下文中。
@MockBean 在需要 mock 特定 bean(例如外部服務(wù))的集成測試中很有用。
要使用 @MockBean 注解,我們必須在 @RunWith() 中指定 SpringRunner 來運(yùn)行測試。代碼示列如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceMockBeanTest {
@MockBean
private UserRepository userRepository;
private User mockUser() {
User user = new User();
user.setName("mock-test-name");
user.setEmail("mock-test-email");
return user;
}
@Test
public void testGetUser_MockBean() {
User mockUser = mockUser();
// 模擬 UserRepository
Mockito.when(userRepository.getUser(mockUser.getName()))
.thenReturn(mockUser);
// 驗(yàn)證結(jié)果
User user = userRepository.getUser(mockUser.getName());
Assert.assertEquals(mockUser.getName(), user.getName());
Assert.assertEquals(mockUser.getEmail(), user.getEmail());
Mockito.verify(userRepository).getUser(mockUser.getName());
}
}
這里需要注意的是,Spring test 默認(rèn)會重用 bean。如果 A 測試使用 mock 對象進(jìn)行測試,而 B 測試使用原有的相同類型對象進(jìn)行測試,B 測試在 A 測試之后運(yùn)行,那么 B 測試拿到的對象是 mock 的對象。一般這種情況是不期望的,所以需要用 @DirtiesContext 修飾上面的測試避免這個問題。
最后,小伙伴們可以在 GitHub 中獲取源碼。
到此這篇關(guān)于Spring 單元測試中如何進(jìn)行 mock的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Spring 單元測試mock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中并發(fā)定時任務(wù)的實(shí)現(xiàn)、動態(tài)定時任務(wù)的實(shí)現(xiàn)(看這一篇就夠了)推薦
這篇文章主要介紹了SpringBoot并發(fā)定時任務(wù)動態(tài)定時任務(wù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Java MultipartFile實(shí)現(xiàn)上傳文件/上傳圖片
這篇文章主要介紹了Java MultipartFile實(shí)現(xiàn)上傳文件/上傳圖片,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12
解決jhipster修改jdl生成的實(shí)體類報(bào)錯:liquibase.exception.ValidationFailed
這篇文章主要介紹了解決jhipster修改jdl生成的實(shí)體類報(bào)錯:liquibase.exception.ValidationFailedException: Validation Failed問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
Spring boot通過AOP防止API重復(fù)請求代碼實(shí)例
這篇文章主要介紹了Spring boot通過AOP防止API重復(fù)請求代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12
Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:三角數(shù)字
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:三角數(shù)字,本文直接給出實(shí)現(xiàn)代碼,代碼中包含詳細(xì)注釋,需要的朋友可以參考下2015-06-06
SpringBoot 使用WebSocket功能(實(shí)現(xiàn)步驟)
本文通過詳細(xì)步驟介紹了SpringBoot 使用WebSocket功能,首先需要導(dǎo)入WebSocket坐標(biāo),編寫WebSocket配置類,用于注冊WebSocket的Bean,結(jié)合示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-02-02
詳解spring集成mina實(shí)現(xiàn)服務(wù)端主動推送(包含心跳檢測)
本篇文章主要介紹了詳解spring集成mina實(shí)現(xiàn)服務(wù)端主動推送(包含心跳檢測),具有一定的參考價值,與興趣的可以了解一下2017-09-09

