使用ArgumentCaptor验证模拟的服务调用失败

时间:2016-10-26 13:25:23

标签: java unit-testing mockito

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

我的目的是使用参数捕获器验证调用,以检查感兴趣的调用是否仅被调用一次。 但是这会失败,因为该方法被多次调用,之后会检查参数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());

4 个答案:

答案 0 :(得分:3)

我用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")));

答案 1 :(得分: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

答案 2 :(得分:1)

如果您在Java 8中使用Mockito 2,那么可以采用一种巧妙的方法。见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。

答案 3 :(得分:-1)

编辑:

我不知道如何完全按照你的要求去做。

我喜欢@ 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());

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