一文让你快速上手 Mockito 单元测试框架(中)

作者|mghio

编辑|包包  

与 Spring 框架集成

Mockito 框架提供了 @MockBean 注解用来将 mock 对象注入到 Spring 容器中,该对象会替换容器中任何现有的相同类型的 bean,该注解在需要模拟特定bean(例如外部服务)的测试场景中很有用。如果使用的是 Spring Boot 2.0+ 并且当前容器中已有相同类型的 bean 的时候,需要设置 spring.main.allow-bean-definition-overriding 为 true(默认为 false)允许 bean 定义覆盖。下面假设要测试通过用户编码查询用户的信息,有一个数据库操作层的 UserRepository,也就是我们等下要 mock 的对象,定义如下:

/** * @author mghio * @date: 2020-05-30 * @version: 1.0 * @description: * @since JDK 1.8 */@Repositorypublic interface UserRepository {  User findUserById(Long id);}

还有用户操作的相关服务 UserService 类,其定义如下所示:

/** * @author mghio * @date: 2020-05-30 * @version: 1.0 * @description: * @since JDK 1.8 */@Servicepublic class UserService {  private UserRepository userRepository;  public UserService(UserRepository userRepository) {    this.userRepository = userRepository;  }  public User findUserById(Long id) {    return userRepository.findUserById(id);  }}

在测试类中使用 @MockBean 来标注 UserRepository 属性表示这个类型的 bean 使用的是 mock 对象,使用 @Autowired 标注表示 UserService 属性使用的是 Spring 容器中的对象,然后使用 @SpringBootTest 启用 Spring 环境即可。

/** * @author mghio * @date: 2020-05-30 * @version: 1.0 * @description: * @since JDK 1.8 */@SpringBootTestpublic class UserServiceUnitTest {  @Autowired  private UserService userService;  @MockBean  private UserRepository userRepository;  @Test  public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {    User expectedUser = new User(9527L, "mghio", "18288888880");    when(userRepository.findUserById(9527L)).thenReturn(expectedUser);    User actualUser = userService.findUserById(9527L);    assertEquals(expectedUser, actualUser);  }}
Mockito 框架的工作原理

通过以上介绍可以发现, Mockito 非常容易使用并且可以方便的验证一些方法的行为,相信你已经看出来了,使用的步骤是先创建一个需要 mock 的对象 Target ,该对象如下:

public class Target {  public String foo(String name) {    return String.format("Hello, %s", name);  }}

然后我们直接使用 Mockito.mock 方法和 when(...).thenReturn(...) 来生成 mock 对象并指定方法调用时的行为,代码如下:

@Testpublic void test_foo() {  String expectedResult = "Mocked mghio";  when(mockTarget.foo("mghio")).thenReturn(expectedResult);  String actualResult = mockTarget.foo("mghio");  assertEquals(expectedResult, actualResult);}

仔细观察以上 when(mockTarget.foo("mghio")).thenReturn(expectedResult) 这行代码,首次使用我也觉得很奇怪,when 方法的入参竟然是方法的返回值 mockTarget.foo("mghio"),觉得正确的代码应该是这样 when(mockTarget).foo("mghio"),但是这个写法实际上无法进行编译。既然 Target.foo 方法的返回值是 String 类型,那是不是可以使用如下方式呢?

Mockito.when("Hello, I am mghio").thenReturn("Mocked mghio");

结果是编译通过,但是在运行时报错:

从错误提示可以看出,when 方法需要一个方法调用的参数,实际上它只需要 more 对象方法调用在 when 方法之前就行,我们看看下面这个测试代码:

@Testpublic void test_mockitoWhenMethod() {  String expectedResult = "Mocked mghio";  mockTarget.foo("mghio");  when("Hello, I am mghio").thenReturn(expectedResult);  String actualResult = mockTarget.foo("mghio");  assertEquals(expectedResult, actualResult);}

以上代码可以正常测试通过,结果如下:

为什么这样就可以正常测试通过?是因为当我们调用 mock 对象的 foo 方法时,Mockito 会拦截方法的调用然后将方法调用的详细信息保存到 mock 对象的上下文中,当调用到 Mockito.when 方法时,实际上是从该上下文中获取最后一个注册的方法调用,然后把 thenReturn 的参数作为其返回值保存,然后当我们的再次调用 mock 对象的该方法时,之前已经记录的方法行为将被再次回放,该方法触发拦截器重新调用并且返回我们在 thenReturn 方法指定的返回值。以下是 Mockito.when 方法的源码:

该方法里面直接使用了 MockitoCore.when 方法,继续跟进,该方法源码如下:

仔细观察可以发现,在源码中并没有用到参数 methodCall,而是从 MockingProgress 实例中获取 OngoingStubbing 对象,这个 OngoingStubbing 对象就是前文所提到的上下文对象。个人感觉是 Mockito 为了提供简洁易用的 API 然后才制造了 when 方法调用的这种“幻象”,简而言之,Mockito 框架通过方法拦截在上下文中存储和检索方法调用详细信息来工作的。

- - - -未完待续- - - - 

喜欢本文的朋友,欢迎关注公众号  并发编程网,收看更多精彩内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值