51Testing软件测试论坛

标题: Mockito框架下如何优雅的验证调用Mock对象方法的入参 [打印本页]

作者: 海上孤帆    时间: 2024-2-29 11:27
标题: Mockito框架下如何优雅的验证调用Mock对象方法的入参
1. 场景

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

  1. <font size="3">// 被测组件
  2. public Class Order {
  3.     private final OrderRepository orderRepository;
  4.    
  5.     public void create(CreateOrderDTO createOrder)
  6.         validateCreateOrDer(createOrder);

  7.         var order = processOrder(createOrder);

  8.          orderRepository.save(order);
  9. }

  10. </font>
复制代码

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

  1. <font size="3">public Class OrderServiceTest {
  2.     private final OrderRepository orderRepository = mock(OrderRepository.class);
  3.     private final OrderService orderService = new OrderService(orderRepsitory);
  4.    
  5.     public void shouldSuccessWhenCreateOrder() {
  6.          when(orderRepository.save(any).thenReturn(any());
  7.          CreateOrderDTO createOrder = ...
  8.          
  9.          orderService.create(createOrder);
  10.    
  11.           // 验证参数是否与期望的一致
  12.           verify(orderRepository, times(1)).save(argThat(order ->
  13.                 order.userId().equals(createOrder.userId()) &&
  14.                 order.price() == 100));
  15.     }
  16. }   
  17. </font>
复制代码

2. 问题

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


测试代码不美观,如果待验证的参数比较多,要用很多个&&连接;

测试用例失败时无法给出是哪一个参数不正确,如上例中,在argThat中的lambda function表达式返回false时,测试用例失败的信息无法让开发者直接知道是userId还是price不符合期望,开发者还需要进行代码调试以确认到底是哪个参数不符合期望。

  1. <font size="3">Argument(s) are different! Wanted:
  2. orderRepository.save(
  3.     <custom argument matcher>
  4. );

  5. Actual invocations have different arguments:
  6. orderRepository.save(
  7.    captor.demo.persistence.entity.Order@441aa7ae
  8. );
  9. </font>
复制代码

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

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


用MockitoExtension.class作为测试的执行引擎;

使用@Captor注解定义一个参数捕获对象,并在CUT调用的被Mock对象的方法中传入定义的参数捕获对象;

从定义的参数捕获对象中获得捕获的参数,使用assert进行验证。

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

  1. <font size="3">// 1. 使用MockitoExtension测试执行引擎
  2. @ExtendWith(MockitoExtension.class)
  3. public Class OrderServiceTest {
  4.     private final OrderRepository orderRepository = mock(OrderRepository.class);
  5.     private final OrderService orderService = new OrderService(orderRepsitory);
  6.    
  7.     //2. 使用@Captor注解定义一个参数捕获对象,泛型类型为待捕获的参数类型
  8.     @Captor
  9.     private ArgumentCaptor<Order> orderCaptor;
  10.    
  11.     public void shouldSuccessWhenCreateOrder() {
  12.          when(orderRepository.save(any).thenReturn(any());
  13.          CreateOrderDTO createOrder = ...
  14.          
  15.          orderService.create(createOrder);
  16.    
  17.           //3. 被Mock对象的方法中传入定义的参数捕获对象,注意需要调用参数捕获对象的capture方法
  18.           verify(orderRepository, times(1)).save(orderCaptor.capture());
  19.           //4. 从定义的参数捕获对象获得捕获的入参,使用assert进行验证
  20.           var order = orderCaptor.getValue();
  21.           assertThat(order.userId()).isEqualTo(createOrder.userId());
  22.           assertThat(order.price()).isEuqalTo(100);
  23.     }
  24. }
  25. </font>
复制代码

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


  1. <font size="3">org.opentest4j.AssertionFailedError:
  2. Expected :100
  3. Actual   :50
  4. </font>
复制代码






欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2