1. 首页 > 资讯 > 生活资讯

避坑指南 Mockito

介绍

Mockito 是一个流行的用于测试 Java 应用程序的框架。它提供了一种强大且易于使用的方式来模拟依赖关系和编写单元测试。然而,刚接触 Mockito 的开发人员可能会犯一些错误,从而导致测试不可靠,甚至导致应用程序出现意外行为。在本文中,我们将讨论开发人员在 Spring Boot 应用程序中使用 Mockito 框架时犯的常见错误,以及代码示例和解释。

1.滥用@Mock和@InjectMocks注释

开发人员在使用 Mockito 时最常见的错误之一是滥用@Mock和@InjectMocks注释。@Mock注解用于为特定类创建模拟对象,而@InjectMocks注解用于将模拟对象注入到被测试的类中。需要注意的是,@InjectMocks 只能与类一起使用,不能与接口一起使用。

@RunWith(MockitoJUnitRunner.class)public class MyServiceTest {@Mockprivate MyRepository myRepository;@InjectMocksprivate MyService myService;// test methods}

2.不重置Mock对象

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);}

3.对Mock对象使用错误的范围

Mockito 默认创建范围为类级别。这意味着同一个Mock对象将用于 类中的所有测试方法。但是,如果模拟对象需要为每个测试方法具有不同的状态或行为,则应使用方法级别的范围来创建。要创建具有正确范围的Mock对象,我们可以使用Spring Boot 提供的@MockBean注解。

@RunWith(SpringRunner.class)@WebMvcTest(UserController.class)public class UserControllerTest {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate UserService userService;@MockBeanprivate UserRepository userRepository;@Testpublic void testGetUserById() throws Exception {// arrangeLong userId = 1L;User user = new User();user.setId(userId);user.setName("John Doe");Mockito.when(userService.getUserById(userId)).thenReturn(user);// actMvcResult result = mockMvc.perform(get("/users/{id}", userId)).andExpect(status().isOk()).andReturn();// assertString response = result.getResponse().getContentAsString();assertThat(response).isEqualTo("{\"id\":1,\"name\":\"John Doe\"}");Mockito.verify(userService, times(1)).getUserById(userId);}@Testpublic void testAddUser() throws Exception {// arrangeUser user = new User();user.setName("Jane Doe");Mockito.when(userService.addUser(user)).thenReturn(user);// actMvcResult result = mockMvc.perform(post("/users").contentType(MediaType.APPLICATION_JSON).content("{\"name\":\"Jane Doe\"}")).andExpect(status().isOk()).andReturn();// assertString 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对象的新实例。

4.不验证Mock对象

Mockito 提供了 Mockito.verify()的方法,可用于验证是否使用特定参数调用了Mock对象。如果Mock对象未经验证,可能会导致不可靠的测试和意外的行为。

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetUserById() {// arrangeLong userId = 1L;User user = new User();user.setId(userId);user.setName("John Doe");Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(user));// actUser result = userService.getUserById(userId);// assertassertThat(result).isEqualTo(user);Mockito.verify(userRepository, times(1)).findById(userId);}@Testpublic void testGetUserByIdNotFound() {// arrangeLong userId = 1L;Mockito.when(userRepository.findById(userId)).thenReturn(Optional.empty());// actUserNotFoundException exception = assertThrows(UserNotFoundException.class, () -> {userService.getUserById(userId);});// assertassertThat(exception.getMessage()).isEqualTo("User not found with ID: " + userId);Mockito.verify(userRepository, times(1)).findById(userId);}}

请注意,我们使用该Mockito.verify()方法来验证两个测试方法是否使用正确的 ID 并仅调用了该类的findById()方法一次。使用times(1)参数来指定该方法应该被调用一次,并传入正确的 ID 作为参数。如果未使用正确的 ID 调用该方法,或者多次调用该方法,则测试将失败。

5.不指定Mock对象的行为

认创建Mock对象,默认行为是“不执行任何操作”。这意味着,如果在Mock对象上调用方法并且未指定任何行为,则该方法将仅返回 null 或其返回类型的默认值。指定 Mock对象的行为来确保它们在测试中按预期运行非常重要。下面是使用Mockito.when()方法指定Mock对象的行为的示例:

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetAllUsers() {// arrangeList<User> users = Arrays.asList(new User(1L, "John Doe"),new User(2L, "Jane Doe"));Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);}@Testpublic void testGetAllUsersEmpty() {// arrangeList<User> users = Collections.emptyList();Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);}}

6.使用错误的方法验证模拟对象

Mockito 提供了几种方法来验证是否使用特定参数调用了Mock对象,例如Mockito.verify()、Mockito.verifyZeroInteractions() 和Mockito.verifyNoMoreInteractions() 。使用正确的方法进行所需的验证非常重要,因为使用错误的方法可能会导致不可靠的测试和意外的行为。Mockito.verify()方法使用示例:

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetAllUsers() {// arrangeList<User> users = Arrays.asList(new User(1L, "John Doe"),new User(2L, "Jane Doe"));Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);Mockito.verify(userRepository).findAll();Mockito.verifyNoMoreInteractions(userRepository);}@Testpublic void testEmptyUserList() {// arrangeList<User> users = Collections.emptyList();Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);Mockito.verify(userRepository).findAll();Mockito.verifyNoMoreInteractions(userRepository);Mockito.verifyZeroInteractions(userRepository);}}

在第二个测试用例中,我们使用Mockito.verifyZeroInteractions()方法来验证测试期间没有与Mock对象发生交互。这确保只测试我们想要测试的行为,并且代码中不会发生意外的交互。

7.不处理异常

以下是使用 Mockito 时如何处理异常的示例:

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetUserById() {// arrangeLong userId = 1L;User user = new User();user.setId(userId);user.setName("John Doe");Mockito.when(userRepository.findById(userId)).thenReturn(Optional.of(user));// actUser result = userService.getUserById(userId);// assertassertThat(result).isEqualTo(user);}@Testpublic void testGetUserByIdNotFound() {// arrangeLong userId = 1L;Mockito.when(userRepository.findById(userId)).thenReturn(Optional.empty());// act and assertUserNotFoundException 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()异常的方法来验证是否返回了正确的消息。

8.不使用正确的匹配器

以下是使用 Mockito 时如何使用正确匹配器的示例:

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testAddUser() {// arrangeUser user = new User();user.setName("John Doe");user.setAge(30);// actuserService.addUser(user);// assertArgumentCaptor<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 {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testDeleteUserById() {// arrangeLong userId = 1L;// actuserService.deleteUserById(userId);// assertMockito.verify(userRepository, Mockito.times(1)).deleteById(Mockito.eq(userId));}}

使用Mockito.eq()方法来指定deleteById()方法调用的参数值。这确保了正确的ID被传递给该方法,并避免了测试中的意外行为。

9.没有对Mock对象使用正确的注解

以下是使用

@RunWith(SpringRunner.class)@SpringBootTestpublic class UserServiceTest {@Autowiredprivate UserService userService;@MockBeanprivate UserRepository userRepository;@Testpublic void testGetAllUsers() {// arrangeList<User> users = Arrays.asList(new User(1L, "John Doe"),new User(2L, "Jane Doe"));Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);}}

使用@RunWith和@SpringBootTest注解来配置单元测试的Spring测试框架。通过使用这些注解,我们可以确保应用程序上下文被加载并且依赖项被正确地注入。

10.未使用正确的测试配置

我们希望使用正确的配置,以确保正确加载应用程序上下文并按预期注入依赖项。以下是使用@ContextConfiguration 的示例:

@RunWith(MockitoJUnitRunner.class)@ContextConfiguration(classes = {UserService.class, UserRepository.class})public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetAllUsers() {// arrangeList<User> users = Arrays.asList(new User(1L, "John Doe"),new User(2L, "Jane Doe"));Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);}}

使用@ContextConfiguration注解来指定测试的配置。我们将一个类数组传递给它,其中包括UserService和UserRepository类,这样可以确保它们被加载到应用程序上下文中。

11.没有使用正确的方法来创建Mock对象

使用正确的方法来创建Mock对象,以确保依赖项的行为是可控的并且测试是可靠的。以下是使用Mockito.mock()的示例:

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {private UserService userService;private UserRepository userRepository;@Beforepublic void setUp() {userRepository = Mockito.mock(UserRepository.class);userService = new UserService(userRepository);}@Testpublic void testGetAllUsers() {// arrangeList<User> users = Arrays.asList(new User(1L, "John Doe"),new User(2L, "Jane Doe"));Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);}}

使用了Mockito.when()方法来指定Mock对象的行为,即当findAll()方法被调用时,返回一个User对象的列表。

12.没有使用正确的方法来存根Mock对象

使用正确的方法来存根Mock对象,以确保依赖项的行为可以控制并且测试是可靠的。以下是使用when().thenReturn()的示例:

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetAllUsers() {// arrangeList<User> users = Arrays.asList(new User(1L, "John Doe"),new User(2L, "Jane Doe"));Mockito.when(userRepository.findAll()).thenReturn(users);// actList<User> result = userService.getAllUsers();// assertassertThat(result).isEqualTo(users);}}

通过使用Mockito提供的when().thenReturn()方法,我们可以指定模拟对象的行为并确保在测试中控制依赖项。

13.没有使用正确的方法来验证Mock对象的交互

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());}

14.没有使用正确的方法来验证Mock对象的交互顺序

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 的开发人员可能会犯错误,从而导致应用程序中的测试不可靠和出现意外行为。

本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://www.jmbhsh.com/shenghuozixun/35519.html

联系我们

QQ号:***

微信号:***

工作日:9:30-18:30,节假日休息