Mockito框架下如何优雅的验证调用Mock对象方法的入参

文章介绍了在单元测试中,使用Mockito框架时如何优雅地验证Mock对象方法的参数。传统方法使用argThat进行参数匹配,但在参数多时代码不美观且调试困难。Mockito的@Captor注解提供了一种更优的解决方案,通过参数捕获来验证方法调用的参数,使代码更简洁,测试失败时能更快定位问题。

该文章已同步至个人微信公众号[不能止步],欢迎关注。

1. 场景

在单元测试场景中,一种典型的场景是为了测试某一个类(Component Under Test, 简称CUT)而需要mock其依赖的的类。示例如下:

// 被测组件
public Class Order {
    private final OrderRepository orderRepository;
    
    public void create(CreateOrderDTO createOrder)
        validateCreateOrDer(createOrder);

        var order = processOrder(createOrder);

         orderRepository.save(order);
}

为了验证CUT业务实现的正确性,通常需要验证传给调用Mock对象的方法的参数的正确性。如果采用Mockito测试框架,一般可以按如下方式实现测试用例:

public Class OrderServiceTest {
    private final OrderRepository orderRepository = mock(OrderRepository.class);
    private final OrderService orderService = new OrderService(orderRepsitory);
    
    public void shouldSuccessWhenCreateOrder() {
         when(orderRepository.save(any).thenReturn(any());
         CreateOrderDTO createOrder = ...
         
         orderService.create(createOrder);
    
          // 验证参数是否与期望的一致
          verify(orderRepository, times(1)).save(argThat(order -> 
                order.userId().equals(createOrder.userId()) &&
                order.price() == 100));
    }
}    

2. 问题

采用上述方法(argThat)测试Mock对象的方法的入参的方式虽然可以验证入参是否符合期望,但细看代码,我们依然会发现几个问题。

  • 测试代码不美观,如果待验证的参数比较多,要用很多个&&连接;
  • 测试用例失败时无法给出是哪一个参数不正确,如上例中,在argThat中的lambda function表达式返回false时,测试用例失败的信息无法让开发者直接知道是userId还是price不符合期望,开发者还需要进行代码调试以确认到底是哪个参数不符合期望。
Argument(s) are different! Wanted:
orderRepository.save(
    <custom argument matcher>
);

Actual invocations have different arguments:
orderRepository.save(
   captor.demo.persistence.entity.Order@441aa7ae
);

3. 如何优雅的验证Mock对象的方法的入参

在Mockito测试框架下,Mockit提供了对Mock对象方法的入参捕获,其做法是:

  • 用MockitoExtension.class作为测试的执行引擎;
  • 使用@Captor注解定义一个参数捕获对象,并在CUT调用的被Mock对象的方法中传入定义的参数捕获对象;
  • 从定义的参数捕获对象中获得捕获的参数,使用assert进行验证。

采用该方法对上述示例的改进如下:

// 1. 使用MockitoExtension测试执行引擎
@ExtendWith(MockitoExtension.class)
public Class OrderServiceTest {
    private final OrderRepository orderRepository = mock(OrderRepository.class);
    private final OrderService orderService = new OrderService(orderRepsitory);
    
    //2. 使用@Captor注解定义一个参数捕获对象,泛型类型为待捕获的参数类型
    @Captor
    private ArgumentCaptor<Order> orderCaptor;
    
    public void shouldSuccessWhenCreateOrder() {
         when(orderRepository.save(any).thenReturn(any());
         CreateOrderDTO createOrder = ...
         
         orderService.create(createOrder);
    
          //3. 被Mock对象的方法中传入定义的参数捕获对象,注意需要调用参数捕获对象的capture方法
          verify(orderRepository, times(1)).save(orderCaptor.capture());
          //4. 从定义的参数捕获对象获得捕获的入参,使用assert进行验证
          var order = orderCaptor.getValue();
          assertThat(order.userId()).isEqualTo(createOrder.userId());
          assertThat(order.price()).isEuqalTo(100);
    }
} 

对比一下argThat和@Captor两种测试Mock对象的方法参数的方法,使用@Captor这种方法具有:

  • 代码美观,修改容易;
  • 测试用例失败时能够快速定位出哪个参数不符合期望。
org.opentest4j.AssertionFailedError: 
Expected :100
Actual   :50
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值