Mockito 是一個(gè)流行的用于測(cè)試 Java 應(yīng)用程序的框架。它提供了一種強(qiáng)大且易于使用的方式來(lái)模擬依賴關(guān)系和編寫單元測(cè)試。然而,剛接觸 Mockito 的開發(fā)人員可能會(huì)犯一些錯(cuò)誤,從而導(dǎo)致測(cè)試不可靠,甚至導(dǎo)致應(yīng)用程序出現(xiàn)意外行為。在本文中,我們將討論開發(fā)人員在 Spring Boot 應(yīng)用程序中使用 Mockito 框架時(shí)犯的常見錯(cuò)誤,以及代碼示例和解釋。
開發(fā)人員在使用 Mockito 時(shí)最常見的錯(cuò)誤之一是濫用@Mock和@InjectMocks注釋。@Mock注解用于為特定類創(chuàng)建模擬對(duì)象,而@InjectMocks注解用于將模擬對(duì)象注入到被測(cè)試的類中。需要注意的是,@InjectMocks 只能與類一起使用,不能與接口一起使用。
例子:
@RunWith(MockitoJUnitRunner.class)public class MyServiceTest { @Mock private MyRepository myRepository; @InjectMocks private MyService myService; // test methods }
Mockito 可創(chuàng)建在多個(gè)測(cè)試中重用的Mock對(duì)象。如果在測(cè)試之間未重置Mock對(duì)象,則可能會(huì)導(dǎo)致意外行為和不可靠的測(cè)試。Mockito 提供了一個(gè)名為Mockito.reset()的方法,可用于重置所有Mock對(duì)象。
例子:
@Beforepublic void setUp() { MockitoAnnotations.initMocks(this);}@Testpublic void test1() { Mockito.when(myRepository.findById(1)).thenReturn(Optional.of(new MyObject())); // test code}@Testpublic void test2() { Mockito.when(myRepository.findById(2)).thenReturn(Optional.of(new MyObject())); // test code}@Afterpublic void tearDown() { Mockito.reset(myRepository);}
Mockito 默認(rèn)創(chuàng)建范圍為類級(jí)別。這意味著同一個(gè)Mock對(duì)象將用于類中的所有測(cè)試方法。但是,如果模擬對(duì)象需要為每個(gè)測(cè)試方法具有不同的狀態(tài)或行為,則應(yīng)使用方法級(jí)別的范圍來(lái)創(chuàng)建。要?jiǎng)?chuàng)建具有正確范圍的Mock對(duì)象,我們可以使用Spring Boot 提供的@MockBean注解。
@MockBean使用示例:
@RunWith(SpringRunner.class)@WebMvcTest(UserController.class)public class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @MockBean private UserRepository userRepository; @Test public void testGetUserById() throws Exception { // arrange Long userId = 1L; User user = new User(); user.setId(userId); user.setName("John Doe"); Mockito.when(userService.getUserById(userId)).thenReturn(user); // act MvcResult result = mockMvc.perform(get("/users/{id}", userId)) .andExpect(status().isOk()) .andReturn(); // assert String response = result.getResponse().getContentAsString(); assertThat(response).isEqualTo("{/"id/":1,/"name/":/"John Doe/"}"); Mockito.verify(userService, times(1)).getUserById(userId); } @Test public void testAddUser() throws Exception { // arrange User user = new User(); user.setName("Jane Doe"); Mockito.when(userService.addUser(user)).thenReturn(user); // act MvcResult result = mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{/"name/":/"Jane Doe/"}")) .andExpect(status().isOk()) .andReturn(); // assert String response = result.getResponse().getContentAsString(); assertThat(response).isEqualTo("{/"id/":null,/"name/":/"Jane Doe/"}"); Mockito.verify(userService, times(1)).addUser(user); }}
在這個(gè)例子中,我們使用@WebMvcTest注解來(lái)測(cè)試UserController類,并注入MockMvc對(duì)象來(lái)模擬HTTP請(qǐng)求。我們還使用@MockBean注釋為UserService和UserRepository類創(chuàng)建模擬對(duì)象。
注意,這里不需要在測(cè)試之間重置Mock對(duì)象,因?yàn)锧MockBean注解會(huì)為每個(gè)測(cè)試方法創(chuàng)建Mock對(duì)象的新實(shí)例。
Mockito 提供了 Mockito.verify()的方法,可用于驗(yàn)證是否使用特定參數(shù)調(diào)用了Mock對(duì)象。如果Mock對(duì)象未經(jīng)驗(yàn)證,可能會(huì)導(dǎo)致不可靠的測(cè)試和意外的行為。
Mockito.verify()使用示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetUserById() { // arrange Long userId = 1L; User user = new User(); user.setId(userId); user.setName("John Doe"); Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // act User result = userService.getUserById(userId); // assert assertThat(result).isEqualTo(user); Mockito.verify(userRepository, times(1)).findById(userId); } @Test public void testGetUserByIdNotFound() { // arrange Long userId = 1L; Mockito.when(userRepository.findById(userId)).thenReturn(Optional.empty()); // act UserNotFoundException exception = assertThrows(UserNotFoundException.class, () -> { userService.getUserById(userId); }); // assert assertThat(exception.getMessage()).isEqualTo("User not found with ID: " + userId); Mockito.verify(userRepository, times(1)).findById(userId); }}
請(qǐng)注意,我們使用該Mockito.verify()方法來(lái)驗(yàn)證兩個(gè)測(cè)試方法是否使用正確的 ID 并僅調(diào)用了該類的findById()方法一次。使用times(1)參數(shù)來(lái)指定該方法應(yīng)該被調(diào)用一次,并傳入正確的 ID 作為參數(shù)。如果未使用正確的 ID 調(diào)用該方法,或者多次調(diào)用該方法,則測(cè)試將失敗。
Mockito 默認(rèn)創(chuàng)建Mock對(duì)象,默認(rèn)行為是“不執(zhí)行任何操作”。這意味著,如果在Mock對(duì)象上調(diào)用方法并且未指定任何行為,則該方法將僅返回 null 或其返回類型的默認(rèn)值。指定Mock對(duì)象的行為來(lái)確保它們?cè)跍y(cè)試中按預(yù)期運(yùn)行非常重要。下面是使用Mockito.when()方法指定Mock對(duì)象的行為的示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetAllUsers() { // arrange List<User> users = Arrays.asList( new User(1L, "John Doe"), new User(2L, "Jane Doe") ); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); } @Test public void testGetAllUsersEmpty() { // arrange List<User> users = Collections.emptyList(); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); }}
Mockito 提供了幾種方法來(lái)驗(yàn)證是否使用特定參數(shù)調(diào)用了Mock對(duì)象,例如Mockito.verify()、Mockito.verifyZeroInteractions () 和Mockito.verifyNoMoreInteractions () 。使用正確的方法進(jìn)行所需的驗(yàn)證非常重要,因?yàn)槭褂缅e(cuò)誤的方法可能會(huì)導(dǎo)致不可靠的測(cè)試和意外的行為。Mockito.verify()方法使用示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetAllUsers() { // arrange List<User> users = Arrays.asList( new User(1L, "John Doe"), new User(2L, "Jane Doe") ); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); Mockito.verify(userRepository).findAll(); Mockito.verifyNoMoreInteractions(userRepository); } @Test public void testEmptyUserList() { // arrange List<User> users = Collections.emptyList(); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); Mockito.verify(userRepository).findAll(); Mockito.verifyNoMoreInteractions(userRepository); Mockito.verifyZeroInteractions(userRepository); }}
在第二個(gè)測(cè)試用例中,我們使用Mockito.verifyZeroInteractions()方法來(lái)驗(yàn)證測(cè)試期間沒有與Mock對(duì)象發(fā)生交互。這確保只測(cè)試我們想要測(cè)試的行為,并且代碼中不會(huì)發(fā)生意外的交互。
以下是使用 Mockito 時(shí)如何處理異常的示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetUserById() { // arrange Long userId = 1L; User user = new User(); user.setId(userId); user.setName("John Doe"); Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // act User result = userService.getUserById(userId); // assert assertThat(result).isEqualTo(user); } @Test public void testGetUserByIdNotFound() { // arrange Long userId = 1L; Mockito.when(userRepository.findById(userId)).thenReturn(Optional.empty()); // act and assert UserNotFoundException exception = assertThrows(UserNotFoundException.class, () -> { userService.getUserById(userId); }); assertThat(exception.getMessage()).isEqualTo("User not found with ID: " + userId); }}
在testGetUserByIdNotFound()方法中,我們Mock UserRepository 類的 findById() 方法以返回一個(gè)空的可選值。然后,我們使用特定 ID 調(diào)用UserService類的getUserById()方法,并且期望該方法拋出UserNotFoundException. 然后使用assertThrows()方法來(lái)驗(yàn)證是否拋出了正確的異常,并且我們還使用getMessage()異常的方法來(lái)驗(yàn)證是否返回了正確的消息。
以下是使用 Mockito 時(shí)如何使用正確匹配器的示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testAddUser() { // arrange User user = new User(); user.setName("John Doe"); user.setAge(30); // act userService.addUser(user); // assert ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class); Mockito.verify(userRepository).save(captor.capture()); assertThat(captor.getValue().getName()).isEqualTo("John Doe"); assertThat(captor.getValue().getAge()).isEqualTo(30); }}
使用ArgumentCaptor類來(lái)捕獲傳遞給UserRepository類的save()方法的參數(shù)值。我們還使用Mockito.eq()方法來(lái)指定方法調(diào)用的參數(shù)值,使用user.getName()和user.getAge()方法來(lái)獲取正確的值。這有助于確保向方法傳遞正確的參數(shù),并避免在測(cè)試中出現(xiàn)意外的行為。
下面是另一個(gè)示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testDeleteUserById() { // arrange Long userId = 1L; // act userService.deleteUserById(userId); // assert Mockito.verify(userRepository, Mockito.times(1)).deleteById(Mockito.eq(userId)); }}
使用Mockito.eq()方法來(lái)指定deleteById()方法調(diào)用的參數(shù)值。這確保了正確的ID被傳遞給該方法,并避免了測(cè)試中的意外行為。
以下是使用@MockBean 和 @RunWith 注解示例:
@RunWith(SpringRunner.class)@SpringBootTestpublic class UserServiceTest { @Autowired private UserService userService; @MockBean private UserRepository userRepository; @Test public void testGetAllUsers() { // arrange List<User> users = Arrays.asList( new User(1L, "John Doe"), new User(2L, "Jane Doe") ); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); }}
使用@RunWith和@SpringBootTest注解來(lái)配置單元測(cè)試的Spring測(cè)試框架。通過使用這些注解,我們可以確保應(yīng)用程序上下文被加載并且依賴項(xiàng)被正確地注入。
我們希望使用正確的配置,以確保正確加載應(yīng)用程序上下文并按預(yù)期注入依賴項(xiàng)。以下是使用@ContextConfiguration 的示例:
@RunWith(MockitoJUnitRunner.class)@ContextConfiguration(classes = {UserService.class, UserRepository.class})public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetAllUsers() { // arrange List<User> users = Arrays.asList( new User(1L, "John Doe"), new User(2L, "Jane Doe") ); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); }}
使用@ContextConfiguration注解來(lái)指定測(cè)試的配置。我們將一個(gè)類數(shù)組傳遞給它,其中包括UserService和UserRepository類,這樣可以確保它們被加載到應(yīng)用程序上下文中。
使用正確的方法來(lái)創(chuàng)建Mock對(duì)象,以確保依賴項(xiàng)的行為是可控的并且測(cè)試是可靠的。以下是使用Mockito.mock()的示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { private UserService userService; private UserRepository userRepository; @Before public void setUp() { userRepository = Mockito.mock(UserRepository.class); userService = new UserService(userRepository); } @Test public void testGetAllUsers() { // arrange List<User> users = Arrays.asList( new User(1L, "John Doe"), new User(2L, "Jane Doe") ); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); }}
使用了Mockito.when()方法來(lái)指定Mock對(duì)象的行為,即當(dāng)findAll()方法被調(diào)用時(shí),返回一個(gè)User對(duì)象的列表。
使用正確的方法來(lái)存根Mock對(duì)象,以確保依賴項(xiàng)的行為可以控制并且測(cè)試是可靠的。以下是使用when().thenReturn()的示例:
@RunWith(MockitoJUnitRunner.class)public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetAllUsers() { // arrange List<User> users = Arrays.asList( new User(1L, "John Doe"), new User(2L, "Jane Doe") ); Mockito.when(userRepository.findAll()).thenReturn(users); // act List<User> result = userService.getAllUsers(); // assert assertThat(result).isEqualTo(users); }}
通過使用Mockito提供的when().thenReturn()方法,我們可以指定模擬對(duì)象的行為并確保在測(cè)試中控制依賴項(xiàng)。
Mockito 提供了幾種驗(yàn)證 Mock對(duì)象交互的方法,例如Mockito.verify()、Mockito.verifyZeroInteractions()和Mockito.verifyNoMoreInteractions()。使用正確的方法來(lái)實(shí)現(xiàn)所需的行為非常重要,因?yàn)槭褂缅e(cuò)誤的方法可能會(huì)導(dǎo)致不可靠的測(cè)試和意外的行為。
@Testpublic void test() { MyObject myObject = new MyObject(); myObject.setName("Name"); Mockito.when(myRepository.findById(1)).thenReturn(Optional.of(myObject)); MyObject result = myService.findById(1); Mockito.verify(myRepository).findById(1); Mockito.verifyNoMoreInteractions(myRepository); Assert.assertEquals("Name", result.getName());}
Mockito 提供了一個(gè)名為Mockito.inOrder()的方法,可用于驗(yàn)證與模擬對(duì)象交互的順序。在驗(yàn)證交互順序時(shí)使用此方法非常重要。
@Testpublic void test() { MyObject myObject1 = new MyObject(); myObject1.setName("Name 1"); MyObject myObject2 = new MyObject(); myObject2.setName("Name 2"); InOrder inOrder = Mockito.inOrder(myRepository); Mockito.when(myRepository.findById(1)).thenReturn(Optional.of(myObject1)); Mockito.when(myRepository.findById(2)).thenReturn(Optional.of(myObject2)); MyObject result1 = myService.findById(1); MyObject result2 = myService.findById(2); inOrder.verify(myRepository).findById(1); inOrder.verify(myRepository).findById(2); Assert.assertEquals("Name 1", result1.getName()); Assert.assertEquals("Name 2", result2.getName());}
Mockito 是一個(gè)強(qiáng)大的測(cè)試框架。但是,剛接觸 Mockito 的開發(fā)人員可能會(huì)犯錯(cuò)誤,從而導(dǎo)致應(yīng)用程序中的測(cè)試不可靠和出現(xiàn)意外行為。
本文鏈接:http://www.tebozhan.com/showinfo-26-16372-0.htmlMockito 避坑指南 - 常見錯(cuò)誤的預(yù)防與處理
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com