【问题标题】:Mockito mock calls actual implementation when not told toMockito mock 在没有被告知时调用实际实现
【发布时间】:2020-05-07 15:45:02
【问题描述】:

我有一个包含以下三个方法的类:

void add(Service... objs)
void add(Collection<Service> objs)
void add(Stream<Service> objs)

如您所料,这些都支持添加零个或多个对象,这些对象可以单独指定,也可以作为数组、集合或流的一部分指定。前两个变体从它们的参数创建一个流,并将它们传递给实际执行添加的第三个变体。

在测试使用此类的对象时,我使用 Spring 的 @MockBean 注解创建了一个 Mockito 模拟对象来表示此类的一个实例。我可以在调试器中看到被测对象包含模拟对象,并且我期望的调用(带有服务类型的单个参数)正在发送给模拟对象。因为应该调用的方法是第一个变体(可变参数),而且我知道可变参数有点棘手,所以我编写了测试代码以检查是否使用正确的参数调用了模拟,如下所示:

ArgumentCaptor<Service> captor = ArgumentCaptor.forClass(Service.class);
verify(theMock).add(captor.capture());
assertThat(captor.getAllValues()).containsExactly(expectedService);

但是,当我运行此代码时,断言失败,因为 captor.getAllValues() 返回的列表不包含服务,而是包含流:失败消息显示:

java.lang.AssertionError: 
Expecting:
  <[java.util.stream.ReferencePipeline$Head@2cfe272f]>
to contain exactly (and in same order):
  <[com.xxx.data.Service@37c5]>
but some elements were not found:
  <[com.xxx.data.Service@37c5]>
and others were not expected:
  <[java.util.stream.ReferencePipeline$Head@2cfe272f]>

当我在调试器中运行代码时,我可以看到被测对象对add(Service...)的调用调用了真正的实现;这会调用add(Stream&lt;Service&gt;),而正是该调用被模拟截获。这解释了为什么我看到失败,但我不明白为什么模拟无法拦截原始调用,或者我可以做些什么来让它这样做。

【问题讨论】:

    标签: java spring unit-testing mockito


    【解决方案1】:

    更新您的 ArgumentCaptor 以接受 Service[]

    ArgumentCaptor<Service[]> serviceCaptor = ArgumentCaptor.forClass(Service[].class);
    

    断言

    Service[] actualServices = serviceCaptor.getAllValues();
    assertEquals(actualServices.length, 1);
    assertEquals(actualServices[0], service);
    

    最好在 Junit 中使用 ErrorCollector 来断言多个

    assert 和 SoftAssectTestng 并在您的断言 softAssert.assertAll()

    之后调用

    【讨论】:

    • 谢谢,但这没有帮助。模拟仍在调用 add(Service...) 方法的实际实现,并且 verify(theMock).add(captor.capture()) 调用抛出 NullPointerException,因为它正在使用 null 参数调用实际方法。
    • 但这就是你捕获可变参数的方式
    • 当然,但这不是我的问题。我的问题是 mock 没有拦截对 varargs 方法的调用,而是调用该方法的真正实现并拦截从该方法到另一个方法的调用。
    【解决方案2】:

    我已经想出了解决问题的方法,但实际问题仍然存在,我认为这可能是一个 Mockito 错误(提出为 https://github.com/mockito/mockito/issues/1929)。

    解决方法是将此方法添加到我的测试类中。我添加了一个泛型方法,因为它不仅是对有问题的 add() 方法的调用,而且是对采用 String 参数的类似重载 remove() 方法的调用。

        private <T, V> void verifyCall(T mock, BiConsumer<T, V> call, 
                                         V expectedArg, Class<V> type)
        {
            ArgumentCaptor<V> captor = ArgumentCaptor.forClass(type);
            call.accept(verify(mock), captor.capture());
            List<?> values = captor.getAllValues();
            try {
                assertThat(values.get(0)).isEqualTo(expectedArg);
            } catch (AssertionFailedError ex) {
                assertThat((Stream<V>) values.get(0)).containsExactly(expectedArg);
            }
        }
    

    无论模拟截获的调用是对方法的可变参数变体(应该是),这都应该起作用——在这种情况下,try 块的主体中​​的断言不会抛出异常——还是对流变体(目前是这样) - 在这种情况下,主体中的断言将引发异常,并且将执行 catch 块中的断言。

    然后,当我想验证我的 mock 的 add() 方法是否使用预期的 Service 对象调用时,我会这样做:

    verifyCall(theMock, Datastore::add, expectedService, Service.class);
    

    同样,对于 remove() 方法:

    verifyCall(theMock, Datastore::remove, expectedDeletedKey, String.class);
    

    非常令人高兴的是,当我最终完成这项工作时,测试失败了,因为我在被测方法中犯了一个错误。这一切都值得。

    【讨论】:

      【解决方案3】:

      哦。我没有发现这两个可变参数方法被声明为最终方法。删除了它,一切都按预期工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-08
        • 2019-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多