来一杯 Mojito, 哦, Mockito 🛤️
一个人最大的缺点,不是自私、多情、野蛮、任性,而是偏执地爱一个不爱自己的人
Spring Boot框架来编写一个简单的示例,演示如何使用DI框架在单元测试中使用Mock实现,避免对外部环境的依赖。
首先,我们创建一个UserService接口,定义了一个getUserById方法用于查询用户信息:
publicinterfaceUserService{UsergetUserById(long userId);}
接着,我们创建一个UserServiceImpl实现类,实现了getUserById方法:
@ServicepublicclassUserServiceImplimplementsUserService{@AutowiredprivateUserDao userDao;@OverridepublicUsergetUserById(long userId){return userDao.getUserById(userId);}}
在这个实现类中,我们注入了一个UserDao对象,用于从数据库中查询用户信息。这里我们假设UserDao已经实现并可用。
现在,我们需要编写一个单元测试来测试getUserById方法。由于这个方法依赖于UserDao对象,为了避免对外部环境的依赖,我们可以使用Mockito框架来模拟UserDao对象的行为。
以下是使用Mockito框架编写的单元测试代码:
@RunWith(SpringRunner.class)publicclassUserServiceTest{@MockBeanprivateUserDao userDao;@AutowiredprivateUserService userService;@BeforepublicvoidsetUp()throwsException{MockitoAnnotations.initMocks(this);}@TestpublicvoidtestGetUserById(){User mockUser =newUser();
mockUser.setId(1L);
mockUser.setUsername("test");
mockUser.setPassword("test");Mockito.when(userDao.getUserById(1L)).thenReturn(mockUser);User user = userService.getUserById(1L);Assert.assertEquals("test", user.getUsername());Assert.assertEquals("test", user.getPassword());}}
在这个测试中,我们使用@MockBean注解来注入一个Mock的UserDao对象,并使用Mockito.when方法模拟getUserById方法的行为。这样,在测试中就可以使用MockUserDao对象,而不需要依赖于外部环境。
通过这种方式,我们可以编写稳健、可靠的单元测试,并将其纳入持续集成系统中进行自动化测试,提高软件的质量和稳定性。同时,我们也能避免对外部环境的依赖,保证单元测试的独立性和可重复性。
关键代码分析
MockitoAnnotations.initMocks(this)
MockitoAnnotations.initMocks(this)方法并不会产生代理类,它主要是用于初始化Mockito注解。在测试中,我们通常使用@Mock、@Spy、@InjectMocks等注解来创建Mock对象,并使用Mockito.when、Mockito.verify等方法来模拟对象的行为和验证方法调用。
但是,如果我们不调用MockitoAnnotations.initMocks(this)方法,这些Mock对象就无法被正确初始化,从而导致测试失败。因此,我们通常在@Before注解方法中调用这个方法,以确保所有的Mock对象都已经被正确初始化。
在具体实现中,MockitoAnnotations.initMocks(this)方法会扫描测试类中所有的@Mock、@Spy、@InjectMocks注解,并根据注解中的类型和名称来创建对应的Mock对象,并将这些对象注入到测试类中。这样,在测试过程中就可以使用这些Mock对象来模拟外部依赖,从而实现单元测试的独立性和可重复性。
Mockito.when(userDao.getUserById(1L)).thenReturn(mockUser);
Mockito.when(userDao.getUserById(1L)).thenReturn(mockUser)这行代码的作用是使用Mockito框架来模拟userDao.getUserById方法的行为。
具体来说,Mockito.when方法会返回一个Mockito的OngoingStubbing对象,用于进一步指定模拟方法的返回值或抛出异常。在这个例子中,我们使用thenReturn方法来指定当userDao.getUserById(1L)方法被调用时,返回一个预先构造好的mockUser对象。
当测试中调用userService.getUserById(1L)方法时,Mockito框架会捕获这个方法调用,并返回预先定义的mockUser对象,从而避免对外部环境的依赖,保证单元测试的独立性。
这里还要补充一点,Mockito.when方法并不是真正的模拟方法调用,它只是记录了一个Stubbing规则,告诉Mockito在测试运行过程中如何处理这个方法调用。只有当真正调用模拟的方法时,Mockito才会根据Stubbing规则返回预先定义的值或抛出异常。
在具体实现中,Mockito.when方法会根据指定的方法调用和参数,生成一个MethodInvocation对象,用于表示这个方法调用的信息。然后,Mockito会将这个MethodInvocation对象和Stubbing规则保存到一个全局的InvocationContainer中,用于在测试过程中捕获和处理对这个方法的调用。
Mockito 怎么知道我的 UserService 调用到了我的 UserDao 方法了呢?
Mockito能够知道UserService是否调用了UserDao方法,是通过代理对象来实现的。
在这个示例中,我们使用了Spring的依赖注入(DI)框架来注入UserService和UserDao对象,使得UserService依赖于UserDao对象。在测试中,我们使用Mockito来创建一个Mock的UserDao对象,并将这个对象注入到UserService中。由于UserService中的UserDao对象被Mockito替换成了Mock对象,我们就能够捕获UserService对UserDao方法的调用。
具体来说,当我们调用UserService中的getUserById方法时,实际上是调用了Mockito所创建的一个代理对象的getUserById方法。这个代理对象能够捕获所有的方法调用信息,并将这些信息保存到Mockito的InvocationContainer中。然后,Mockito根据这些调用信息来判断是否需要进行Stubbing,即模拟方法调用的返回值或抛出异常。
当我们使用Mockito.when方法来定义UserDao的getUserById方法的返回值时,Mockito就会根据InvocationContainer中的调用信息来确定是否应用这个Stubbing规则。如果当前的调用信息和Mockito.when方法定义的规则匹配,Mockito就会返回预定义的值或抛出预定义的异常。
因此,Mockito能够知道UserService是否调用了UserDao方法,是通过代理对象捕获所有的方法调用信息,并根据这些信息来判断是否需要进行Stubbing来实现的。
Mockito 调用模拟对象全解析
- 使用Java反射机制创建一个代理对象,代理对象的类型是Mock对象的接口或类。
- 代理对象能够记录Mock对象的所有方法调用信息,并将这些信息保存到一个全局的InvocationContainer对象中。
- 在使用Mockito.when方法定义Mock对象的方法行为时,Mockito会将这些Stubbing规则保存到InvocationContainer对象中,以便在测试过程中捕获和处理方法调用。
- 在测试过程中,当我们调用Mock对象的方法时,代理对象会捕获这个方法调用信息,Mockito会根据InvocationContainer中保存的所有Invocation对象来匹配相应的Stubbing规则,并返回预定义的值或抛出预定义的异常。
- 在测试结束时,我们可以使用Mockito.verify方法来验证Mock对象的方法调用是否符合预期,从而实现单元测试的有效性和可靠性。
因此,Mockito能够调用到我们通过when注册到InvocationContainer的调用信息,是通过代理对象捕获方法调用信息,并将它们保存到InvocationContainer中实现的。在测试过程中,我们可以使用Mockito提供的各种方法来定义Mock对象的行为和验证方法调用,从而实现单元测试的独立性和可重复性。
版权归原作者 洪宏鸿 所有, 如有侵权,请联系我们删除。