Mockito 是一個流行的用于測試 Java 應用程序的框架。它提供了一種強大且易于使用的方式來模擬依賴關系和編寫單元測試。然而,剛接觸 Mockito 的開發(fā)人員可能會犯一些錯誤,從而導致測試不可靠,甚至導致應用程序出現意外行為。在本文中,我們將討論開發(fā)人員在 Spring Boot 應用程序中使用 Mockito 框架時犯的常見錯誤,以及代碼示例和解釋。
開發(fā)人員在使用 Mockito 時最常見的錯誤之一是濫用@Mock和@InjectMocks注釋。@Mock注解用于為特定類創(chuàng)建模擬對象,而@InjectMocks注解用于將模擬對象注入到被測試的類中。需要注意的是,@InjectMocks 只能與類一起使用,不能與接口一起使用。
例子:
@RunWith(MockitoJUnitRunner.class)public class MyServiceTest { @Mock private MyRepository myRepository; @InjectMocks private MyService myService; // test methods }
Mockito 可創(chuàng)建在多個測試中重用的Mock對象。如果在測試之間未重置Mock對象,則可能會導致意外行為和不可靠的測試。Mockito 提供了一個名為Mockito.reset()的方法,可用于重置所有Mock對象。
例子:
@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 默認創(chuàng)建范圍為類級別。這意味著同一個Mock對象將用于類中的所有測試方法。但是,如果模擬對象需要為每個測試方法具有不同的狀態(tài)或行為,則應使用方法級別的范圍來創(chuàng)建。要創(chuàng)建具有正確范圍的Mock對象,我們可以使用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); }}
在這個例子中,我們使用@WebMvcTest注解來測試UserController類,并注入MockMvc對象來模擬HTTP請求。我們還使用@MockBean注釋為UserService和UserRepository類創(chuàng)建模擬對象。
注意,這里不需要在測試之間重置Mock對象,因為@MockBean注解會為每個測試方法創(chuàng)建Mock對象的新實例。
Mockito 提供了 Mockito.verify()的方法,可用于驗證是否使用特定參數調用了Mock對象。如果Mock對象未經驗證,可能會導致不可靠的測試和意外的行為。
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); }}
請注意,我們使用該Mockito.verify()方法來驗證兩個測試方法是否使用正確的 ID 并僅調用了該類的findById()方法一次。使用times(1)參數來指定該方法應該被調用一次,并傳入正確的 ID 作為參數。如果未使用正確的 ID 調用該方法,或者多次調用該方法,則測試將失敗。
Mockito 默認創(chuàng)建Mock對象,默認行為是“不執(zhí)行任何操作”。這意味著,如果在Mock對象上調用方法并且未指定任何行為,則該方法將僅返回 null 或其返回類型的默認值。指定Mock對象的行為來確保它們在測試中按預期運行非常重要。下面是使用Mockito.when()方法指定Mock對象的行為的示例:
@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 提供了幾種方法來驗證是否使用特定參數調用了Mock對象,例如Mockito.verify()、Mockito.verifyZeroInteractions () 和Mockito.verifyNoMoreInteractions () 。使用正確的方法進行所需的驗證非常重要,因為使用錯誤的方法可能會導致不可靠的測試和意外的行為。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); }}
在第二個測試用例中,我們使用Mockito.verifyZeroInteractions()方法來驗證測試期間沒有與Mock對象發(fā)生交互。這確保只測試我們想要測試的行為,并且代碼中不會發(fā)生意外的交互。
以下是使用 Mockito 時如何處理異常的示例:
@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() 方法以返回一個空的可選值。然后,我們使用特定 ID 調用UserService類的getUserById()方法,并且期望該方法拋出UserNotFoundException. 然后使用assertThrows()方法來驗證是否拋出了正確的異常,并且我們還使用getMessage()異常的方法來驗證是否返回了正確的消息。
以下是使用 Mockito 時如何使用正確匹配器的示例:
@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類來捕獲傳遞給UserRepository類的save()方法的參數值。我們還使用Mockito.eq()方法來指定方法調用的參數值,使用user.getName()和user.getAge()方法來獲取正確的值。這有助于確保向方法傳遞正確的參數,并避免在測試中出現意外的行為。
下面是另一個示例:
@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()方法來指定deleteById()方法調用的參數值。這確保了正確的ID被傳遞給該方法,并避免了測試中的意外行為。
以下是使用@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注解來配置單元測試的Spring測試框架。通過使用這些注解,我們可以確保應用程序上下文被加載并且依賴項被正確地注入。
我們希望使用正確的配置,以確保正確加載應用程序上下文并按預期注入依賴項。以下是使用@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注解來指定測試的配置。我們將一個類數組傳遞給它,其中包括UserService和UserRepository類,這樣可以確保它們被加載到應用程序上下文中。
使用正確的方法來創(chuàng)建Mock對象,以確保依賴項的行為是可控的并且測試是可靠的。以下是使用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()方法來指定Mock對象的行為,即當findAll()方法被調用時,返回一個User對象的列表。
使用正確的方法來存根Mock對象,以確保依賴項的行為可以控制并且測試是可靠的。以下是使用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()方法,我們可以指定模擬對象的行為并確保在測試中控制依賴項。
Mockito 提供了幾種驗證 Mock對象交互的方法,例如Mockito.verify()、Mockito.verifyZeroInteractions()和Mockito.verifyNoMoreInteractions()。使用正確的方法來實現所需的行為非常重要,因為使用錯誤的方法可能會導致不可靠的測試和意外的行為。
@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 提供了一個名為Mockito.inOrder()的方法,可用于驗證與模擬對象交互的順序。在驗證交互順序時使用此方法非常重要。
@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 是一個強大的測試框架。但是,剛接觸 Mockito 的開發(fā)人員可能會犯錯誤,從而導致應用程序中的測試不可靠和出現意外行為。
本文鏈接:http://www.www897cc.com/showinfo-26-16372-0.htmlMockito 避坑指南 - 常見錯誤的預防與處理
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com