【问题标题】:Verify mocked service call with ArgumentCaptor fails使用 ArgumentCaptor 验证模拟服务调用失败
【发布时间】:2016-10-26 13:25:22
【问题描述】:

在测试中,我使用了一个模拟服务。我想检查是否使用参数之一的特定属性调用了一次服务。 但是,该方法也与其他参数一起多次调用,但我只对上面的一次调用感兴趣。

我的意图是使用参数捕获器来验证调用,以检查感兴趣的调用是否仅被调用一次。 然而这失败了,因为该方法被多次调用,并且参数captor被之后检查。

见下例:

// service method
void serviceMethod(String someString, MyType myType);

// parameter type
class MyType {
  private String id;
  ...
  String getID() {
    return id;
  }
}

// test verification
ArgumentCaptor<MyType> paramCaptor = ArgumentCaptor.forClass(MyType.class);
// fail in verify 
Mockito.verify(serviceMock, times(1)).serviceMethod(eq("someConstantValue"), paramCaptor);
assertEquals("123", paramCaptor.getValue().getID());

【问题讨论】:

    标签: java unit-testing mockito


    【解决方案1】:

    我用 hamcrest Matcher 解决了这个问题。匹配器在验证方法调用时检查参数值,而参数捕获器仅读取参数以供以后评估。 在这种情况下,所有其他具有未指定值的调用都会被过滤掉。

    static Matcher<MyType> typeIdIs(final String id) {
      return new ArgumentMatcher<MyType>() {
        @Override
        public boolean matches(Object argument) {
          return id.equals(((MyType)argument).getID());
        }
      };
    }
    
    Mockito.verify(serviceMock, times(1)).serviceMethod(eq("someConstantValue"), argThat(typeIdIs("123")));
    

    【讨论】:

    • 这是一个很好的方法。一般来说,参数捕获者是一种反模式。您应该可以通过验证使用参数解决此问题。
    【解决方案2】:

    您可以使用 atLeastOnce()getAllValues() 的组合来做到这一点:

    MyService service = mock( MyService.class );
    ArgumentCaptor<MyType> captor = ArgumentCaptor.forClass( MyType.class );
    
    service.serviceMethod( "foo", new MyType( "123" ) );
    service.serviceMethod( "bar", new MyType( "312" ) );
    service.serviceMethod( "baz", new MyType( "231" ) );
    
    verify( service, atLeastOnce() ).serviceMethod( anyString(), captor.capture() );
    List<String> ids = captor.getAllValues().stream().map( MyType::getId ).collect( Collectors.toList() );
    assertThat( ids ).contains( "123" );
    

    请注意,我正在使用 static imports 以及 AssertJ 断言和匹配器,但您应该能够轻松地将其映射到 JUnit 或其他东西。

    编辑:如果你想确保"123" 只出现一次,你可以使用Collections#frequency( Collection, Object )

    assertThat( Collections.frequency( ids, "123" ) ).isEqualTo( 1 );
    

    甚至更好:看看 AssertJ conditions

    【讨论】:

    • 感谢您的回答。从来没有想过收集所有值以供以后检查。我用 hamcrest matcher 解决了这个问题。
    • @Stefan 不客气。添加了另一种方法来确保 ID 只出现一次。
    【解决方案3】:

    如果您将 Mockito 2 与 Java 8 一起使用,那么有一种巧妙的方法可以做到这一点。见http://static.javadoc.io/org.mockito/mockito-core/2.2.9/org/mockito/Mockito.html#36

    示例将被重构为:

    // service method
    void serviceMethod(String someString, MyType myType);
    
    // parameter type
    class MyType {
      private String id;
      ...
      String getID() {
        return id;
      }
    }
    
    // using a Java 8 lambda to test the ID within a custom ArgumentMatcher
    // passed to argThat
    // Note: you don't need to say "times(1)" as this assumes 1 time
    // times(1) in the argument captor version would also confuse things 
    // if you had other calls and you
    // were just checking for whether a call like THIS had happens    
    Mockito.verify(serviceMock).serviceMethod(eq("someConstantValue"), 
            argThat(input -> input.getID().equals("123")));
    

    lambda 使其更简洁,但您需要 Java 8 和 Mockito 2。

    【讨论】:

      【解决方案4】:

      编辑:

      我不知道有什么方法可以完全按照您的要求进行操作。

      我喜欢@beatngu13 的回答。

      但是,另一个选项可能是将 atLeastOnce() 与有序验证结合使用:

      InOrder inOrder = inOrder(serviceMock);
      inOrder.verify(serviceMock, atLeastOnce()).serviceMethod(eq("someConstantValue"), paramCaptor);
      inOrder.verify(serviceMock).serviceMethod(eq("someConstantValue"), fakeParamOne);
      inOrder.verify(serviceMock).serviceMethod(eq("someConstantValue"), fakeParamTwo);
      
      assertEquals("123", paramCaptor.getValue().getID());
      

      您在其中创建一个 fakeParam 对象,该对象看起来类似于您对服务的其他调用的参数。

      【讨论】:

      • 我想验证该服务只被调用了一次并带有指定的参数集。
      猜你喜欢
      • 2020-06-29
      • 2011-11-22
      • 2011-07-07
      • 1970-01-01
      • 1970-01-01
      • 2018-08-26
      • 1970-01-01
      • 1970-01-01
      • 2016-09-23
      相关资源
      最近更新 更多