Mockito 是一个流行的用于测试 Java 应用程序的框架。它提供了一种强大且易于使用的方式来模拟依赖关系和编写单元测试。然而,刚接触 Mockito 的开发人员可能会犯一些错误,从而导致测试不可靠,甚至导致应用程序出现意外行为。在本文中,我们将讨论开发人员在 Spring Boot 应用程序中使用 Mockito 框架时犯的常见错误,以及代码示例和解释。
开发人员在使用 Mockito 时最常见的错误之一是滥用@Mock和@InjectMocks注释。@Mock注解用于为特定类创建模拟对象,而@InjectMocks注解用于将模拟对象注入到被测试的类中。需要注意的是,@InjectMocks 只能与类一起使用,不能与接口一起使用。
例子:
@RunWith(MockitoJUnitRunner.class)public class MyServiceTest { @Mock private MyRepository myRepository; @InjectMocks private MyService myService; // test methods }
Mockito 可创建在多个测试中重用的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 默认创建范围为类级别。这意味着同一个Mock对象将用于类中的所有测试方法。但是,如果模拟对象需要为每个测试方法具有不同的状态或行为,则应使用方法级别的范围来创建。要创建具有正确范围的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类创建模拟对象。
注意,这里不需要在测试之间重置Mock对象,因为@MockBean注解会为每个测试方法创建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 默认创建Mock对象,默认行为是“不执行任何操作”。这意味着,如果在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对象发生交互。这确保只测试我们想要测试的行为,并且代码中不会发生意外的交互。
以下是使用 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类,这样可以确保它们被加载到应用程序上下文中。
使用正确的方法来创建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 的开发人员可能会犯错误,从而导致应用程序中的测试不可靠和出现意外行为。
本文链接:http://www.28at.com/showinfo-26-16372-0.htmlMockito 避坑指南 - 常见错误的预防与处理
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com