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 的开发人员或许会犯失误,从而造成运行程序中的测试无法靠和出现意在行为。