Mockito Matchers中isA、any、eq和same有什么区别?

66

我不确定它们之间的区别是什么,以及在哪种情况下选择哪个。有些差异可能很明显,比如anyeq,但我把它们都包括进来只是为了确认。

我想知道它们之间的差异,因为我遇到了这个问题: 我在一个Controller类中有这个POST方法。

public Response doSomething(@ResponseBody Request request) {
    return someService.doSomething(request);
}

我希望你能为那个控制器进行单元测试。 我有两个版本。第一个版本很简单,就像这样

@Test
public void testDoSomething() {
    //initialize ObjectMapper mapper
    //initialize Request req and Response res
    
    when(someServiceMock.doSomething(req)).thenReturn(res);

    Response actualRes = someController.doSomething(req);
    assertThat(actualRes, is(res));
}

但我想使用类似这样的MockMvc方法

@Test
public void testDoSomething() {
    //initialize ObjectMapper mapper
    //initialize Request req and Response res
    
    when(someServiceMock.doSomething(any(Request.class))).thenReturn(res);

    mockMvc.perform(post("/do/something")
            .contentType(MediaType.APPLICATION_JSON)
            .content(mapper.writeValueAsString(req))
    )
            .andExpect(status().isOk())
            .andExpect(jsonPath("$message", is("done")));
}

两种方法都可以很好地工作。但是我希望在MockMvc方法中,我的someServiceMock.doSomething()能够收到req或者至少是一个具有与req相同变量值的对象(而不仅仅是任何Request类)并且返回res,就像第一种方法一样。我知道使用MockMvc方法做到这一点是不可能的(或者说可以吗?),因为实际调用中传递的对象与模拟中传递的对象总是不同的。我能否以某种方式实现这一点?或者这样做是否有意义?或者我应该满足于使用any(Request.class)?我已经尝试过eqsame,但它们都失败了。

2个回答

114
  • any() 不检查任何内容。自 Mockito 2.0 起,any(T.class) 具有 isA 语义,表示“任何 T”或正确地说是“任何类型为 T 的实例”。

    这是相对于 Mockito 1.x 的变化,在 Mockito 1.x 中,any(T.class) 不检查任何内容,但在 Java 8 之前保存了一个转换:“任何类型的对象,不一定是给定类的对象。提供类参数仅用于避免强制转换。”

  • isA(T.class) 检查参数是否为 T 的实例,意味着它非空。

  • same(obj) 检查参数是否引用与 obj 相同的实例,即 arg == obj 为 true。

  • eq(obj) 检查参数是否根据其 equals 方法等于 obj。如果您传递实际值而不使用匹配器,则也是这种行为。

    请注意,除非重写 equals,否则您将看到默认的 Object.equals 实现,它将具有与 same(obj) 相同的行为。

如果您需要更精确的定制,可以使用自己的谓词适配器:

  • 对于Mockito 1.x,请使用带有自定义Hamcrest Matcher<T>argThat选择您需要的对象。
  • 对于Mockito 2.0及以上版本,请使用带有自定义org.mockito.ArgumentMatcher<T>Matchers.argThat,或使用带有自定义Hamcrest Matcher<T>MockitoHamcrest.argThat

您还可以使用refEq,它使用反射来确认对象相等性;Hamcrest也有类似的实现,使用SamePropertyValuesAs来匹配具有公共bean-style属性的对象。请注意,在GitHub上问题#1800建议弃用和删除refEq,并且与该问题一样,您可能更喜欢使用eq来更好地封装类对其相等性的感知。


这是一个实用的答案:不仅区分了Mockito的4个“参数匹配器”,而且还提示了使用equals时的风险以及使用argThat进行自定义的方法。 - hc_dev
1
请添加 refEq()。当比较的对象没有实现 equals() 方法时,可以使用此匹配器。该匹配器使用 Java 反射 API 来比较所需对象和实际对象的字段。 - Ivan
1
@Ivan 虽然原问题中没有提到,但我已经加入了对 refEq 的提及,包括 建议从 Mockito 中弃用并删除它的提案 - Jeff Bowman
那么,我们应该使用最新版本的什么? - Jack
@Robert 上面的列表告诉你它们之间的语义差异。你需要将它们与你的使用情况相匹配。 - Jeff Bowman

3
如果你的Request.class实现了equals方法,那么你可以使用eq()函数:
Bar bar = getBar();
when(fooService.fooFxn(eq(bar)).then...

以上内容中的when指的是何时触发。
fooService.fooFxn(otherBar);

如果

otherBar.equals(bar);

或者,如果你希望模拟器适用于其他输入子集(例如,所有Bar.getBarLength()>10的Bars),你可以创建一个Matcher。我不经常看到这种模式,所以通常我会将Matcher创建为一个私有类:

private static class BarMatcher extends BaseMatcher<Bar>{
...//constructors, descriptions, etc.
  public boolean matches(Object otherBar){
     //Checks, casts, etc.
     return otherBar.getBarLength()>10;
  }
}

您可以按照以下方式使用此匹配器:
when(fooService.fooFxn(argThat(new BarMatcher())).then...

希望能对您有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接