Spring 单元测试中如何进行 mock的实现
我们在使用Spring开发项目时,都会用到依赖注入。如果程序依赖了外部系统或者不可控组件,比如依赖数据库、网络通信、文件系统等,我们在编写单元测试时,并不需要实际对外部系统进行操作,这时就要将被测试代码与外部系统进行解耦,而这种解耦方法就叫作“mock”。所谓“mock”就是用一个“假”的服务代替真正的服务。
那我们如何来mock服务进行单元测试呢?mock的方式主要有两种:手动mock和利用单元测试框架mock。其中,利用框架mock主要是为了简化代码编写。我们这里主要是介绍利用框架mock,而手动mock只是简单介绍。
手动mock
手动mock其实就是重新创建一个类继承被mock的服务类,并重写里面的方法。在单元测试中,利用依赖注入的方式使用mock的服务类替换原来的服务类。具体代码示列如下:
/** *UserRepository * *@authorstar */ @Repository publicclassUserRepository{ /** *模拟从数据库中获取用户信息,实际开发中需要连接真实的数据库 */ publicUsergetUser(Stringname){ Useruser=newUser(); user.setName("testing"); user.setEmail("testing@outlook.com"); returnuser; } } /** *MockUserRepository * *@authorstar */ publicclassMockUserRepositoryextendsUserRepository{ /** *模拟从数据库中获取用户信息 */ @Override publicUsergetUser(Stringname){ Useruser=newUser(); user.setName("mock-test-name"); user.setEmail("mock-test-email"); returnuser; } } //进行单元测试 @RunWith(SpringRunner.class) @SpringBootTest publicclassUserServiceManualTest{ @Autowired privateUserServiceuserService; @Test publicvoidtestGetUser_Manual(){ //将MockUserRepository注入到UserService中 userService.setUserRepository(newMockUserRepository()); Useruser=userService.getUser("mock-test-name"); Assert.assertEquals("mock-test-name",user.getName()); Assert.assertEquals("mock-test-email",user.getEmail()); } }
从上面的代码中,我们可以看到手动mock需要编写大量的额外代码,同时被测试类也需要提供依赖注入的入口(setter方法等)。如果被mock的类修改了函数名称或者功能,mock类也要跟着修改,增加了维护成本。
为了提高效率,减少维护成本,我们推荐使用单元测是框架进行mock。
利用框架mock
这里我们主要介绍Mokito.mock()、@Mock、@MockBean这三种方式的mock。
Mocito.mock()
Mocito.mock()方法允许我们创建类或接口的mock对象。然后,我们可以使用mock对象指定其方法的返回值,并验证其方法是否被调用。代码示列如下:
@Test publicvoidtestGetUser_MockMethod(){ //模拟UserRepository,测试时不直接操作数据库 UserRepositorymockUserRepository=Mockito.mock(UserRepository.class); //将mockUserRepository注入到UserService类中 userService.setUserRepository(mockUserRepository); UsermockUser=mockUser(); Mockito.when(mockUserRepository.getUser(mockUser.getName())) .thenReturn(mockUser); Useruser=userService.getUser(mockUser.getName()); Assert.assertEquals(mockUser.getName(),user.getName()); Assert.assertEquals(mockUser.getEmail(),user.getEmail()); //验证mockUserRepository.getUser()方法是否执行 Mockito.verify(mockUserRepository).getUser(mockUser.getName()); }
@Mock
@Mock是Mockito.mock()方法的简写。同样,我们应该只在测试类中使用它。与Mockito.mock()方法不同的是,我们需要在测试期间启用Mockito注解才能使用@Mock注解。
我们可以调用MockitoAnnotations.initMocks(this)静态方法来启用Mockito注解。为了避免测试之间的副作用,建议在每次测试执行之前先进行以下操作:
@Before publicvoidsetup(){ //启用Mockito注解 MockitoAnnotations.initMocks(this); }
我们还可以使用另一种方法来启用Mockito注解。通过在@RunWith()指定MockitoJUnitRunner来运行测试:
@RunWith(MockitoJUnitRunner.class) publicclassUserServiceMockTest{ }
下面我们来看看如何使用@Mock进行服务mock。代码示列如下:
@RunWith(SpringRunner.class) @SpringBootTest publicclassUserServiceMockTest{ @Mock privateUserRepositoryuserRepository; @Autowired @InjectMocks privateUserServiceuserService; privateUsermockUser(){ Useruser=newUser(); user.setName("mock-test-name"); user.setEmail("mock-test-email"); returnuser; } @Before publicvoidsetup(){ //启用Mockito注解 MockitoAnnotations.initMocks(this); } @Test publicvoidtestGetUser_MockAnnotation(){ UsermockUser=mockUser(); Mockito.when(userRepository.getUser(mockUser.getName())) .thenReturn(mockUser); Useruser=userService.getUser(mockUser.getName()); Assert.assertEquals(mockUser.getName(),user.getName()); Assert.assertEquals(mockUser.getEmail(),user.getEmail()); //验证mockUserRepository.getUser()方法是否执行 Mockito.verify(userRepository).getUser(mockUser.getName()); } }
Mockito的@InjectMocks注解作用是将@Mock所修饰的mock对象注入到指定类中替换原有的对象。
@MockBean
@MockBean是SpringBoot中的注解。我们可以使用@MockBean将mock对象添加到Spring应用程序上下文中。该mock对象将替换应用程序上下文中任何现有的相同类型的bean。如果应用程序上下文中没有相同类型的bean,它将使用mock的对象作为bean添加到上下文中。
@MockBean在需要mock特定bean(例如外部服务)的集成测试中很有用。
要使用@MockBean注解,我们必须在@RunWith()中指定SpringRunner来运行测试。代码示列如下:
@RunWith(SpringRunner.class) @SpringBootTest publicclassUserServiceMockBeanTest{ @MockBean privateUserRepositoryuserRepository; privateUsermockUser(){ Useruser=newUser(); user.setName("mock-test-name"); user.setEmail("mock-test-email"); returnuser; } @Test publicvoidtestGetUser_MockBean(){ UsermockUser=mockUser(); //模拟UserRepository Mockito.when(userRepository.getUser(mockUser.getName())) .thenReturn(mockUser); //验证结果 Useruser=userRepository.getUser(mockUser.getName()); Assert.assertEquals(mockUser.getName(),user.getName()); Assert.assertEquals(mockUser.getEmail(),user.getEmail()); Mockito.verify(userRepository).getUser(mockUser.getName()); } }
这里需要注意的是,Springtest默认会重用bean。如果A测试使用mock对象进行测试,而B测试使用原有的相同类型对象进行测试,B测试在A测试之后运行,那么B测试拿到的对象是mock的对象。一般这种情况是不期望的,所以需要用@DirtiesContext修饰上面的测试避免这个问题。
最后,小伙伴们可以在GitHub中获取源码。
到此这篇关于Spring单元测试中如何进行mock的实现的文章就介绍到这了,更多相关Spring单元测试mock内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!