莫基托。验证方法参数

Posted

技术标签:

【中文标题】莫基托。验证方法参数【英文标题】:Mockito. Verify method arguments 【发布时间】:2011-04-03 02:21:55 【问题描述】:

我用谷歌搜索过这个,但没有找到任何相关的东西。我有这样的东西:

Object obj = getObject();
Mockeable mock= Mockito.mock(Mockeable.class);
Mockito.when(mock.mymethod(obj )).thenReturn(null);

Testeable testableObj = new Testeable();
testableObj.setMockeable(mock);
command.runtestmethod();

现在,我想验证在runtestmethod() 内部调用的mymethod(Object o) 是用对象o 调用的,而不是其他对象。但我总是通过测试,无论我在验证中添加什么,例如:

Mockito.verify(mock.mymethod(Mockito.eq(obj)));

Mockito.verify(mock.mymethod(Mockito.eq(null)));

Mockito.verify(mock.mymethod(Mockito.eq("something_else")));

我总是通过考试。我怎样才能完成该验证(如果可能)?

谢谢。

【问题讨论】:

【参考方案1】:

ArgumentMatcher 的替代品是ArgumentCaptor

官方示例:

ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
verify(mock).doSomething(argument.capture());
assertEquals("John", argument.getValue().getName());

也可以使用@Captor 注解来定义捕获者:

@Captor ArgumentCaptor<Person> captor;
//... MockitoAnnotations.initMocks(this);
@Test public void test() 
    //...
    verify(mock).doSomething(captor.capture());
    assertEquals("John", captor.getValue().getName());

【讨论】:

感谢样品!从来没有用过。在代码中包含 captor 之类的东西感觉有点奇怪,但它有帮助。 哈哈,这个问题我没看懂,不过回答对我帮助很大。谢谢:-) 重要:使用模拟调用 verify()/capture() after。我在想它必须先“安装”... 感谢您的回答! 这是一个很好的答案!!非常感谢!【参考方案2】:

您是否尝试使用对象的 .equals 方法进行逻辑相等?您可以使用 Mockito 中包含的 argThat 匹配器来完成此操作

import static org.mockito.Matchers.argThat

接下来,您可以实现自己的参数匹配器,该匹配器将遵循每个对象的 .equals 方法

private class ObjectEqualityArgumentMatcher<T> extends ArgumentMatcher<T> 
    T thisObject;

    public ObjectEqualityArgumentMatcher(T thisObject) 
        this.thisObject = thisObject;
    

    @Override
    public boolean matches(Object argument) 
        return thisObject.equals(argument);
    

现在使用您的代码,您可以更新它以读取...

Object obj = getObject();
Mockeable mock= Mockito.mock(Mockeable.class);
Mockito.when(mock.mymethod(obj)).thenReturn(null);

Testeable obj = new Testeable();
obj.setMockeable(mock);
command.runtestmethod();

verify(mock).mymethod(argThat(new ObjectEqualityArgumentMatcher<Object>(obj)));

如果你只是为了精确相等(内存中的相同对象),就这样做

verify(mock).mymethod(obj);

这将验证它被调用过一次。

【讨论】:

您可以为此目的使用ReflectionEquals 类中的构建。 +1 为您解答。但我想补充一点,verify(mock).mymethod(obj); 不检查完全相等(内存中的相同对象)。相反,它使用了可能被覆盖的对象等方法。 您还可以创建ArgumentMatcher 的匿名实现以减少冗长。 更多细节:默认情况下,verify() 调用 /inbound 参数的/equals() 方法,而不是 /recorded 对象的/equals() 方法。这无关紧要,除非您试图确认您的测试主体返回一个特定的对象实例,并且该主体返回应该是该实例的透明装饰器。 verify 参数的 equals() 不会知道装饰器;而装饰者的equals() 将被重写以容忍原件。在这种情况下,您的测试将错误地失败。【参考方案3】:

argThat加lambda

这就是你的论证验证失败的原因:

    verify(mock).mymethod(argThat(
                            x -> false ));

在哪里

import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;

argThat 加断言

上面的测试会“说”Expected: lambda$... Was: YourClass.toSting...。如果在 lambda 中使用断言,您可以获得更具体的失败原因:

    verify(mock).mymethod(argThat( x -> 
      assertThat(x).isNotNull();
      assertThat(x.description).contains("KEY");
      return true;
    ));

❗️但是❗️:只有在

预计会调用 1 次,或者 预计调用 2 次以上,但验证器始终匹配(返回 true)。

如果验证的方法调用了 2 次以上,mockito 会将所有调用的组合传递给每个验证者。所以 mockito 期望您的验证器为参数集之一静默返回 true,并为其他有效调用返回 false(无断言异常)。这种期望对于 1 次方法调用来说不是问题 - 它应该只返回 true 1 次。

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;

现在失败的测试将显示:Expected: Obj.description to contain 'KEY'. Was: 'Actual description'。注意:我使用了assertJ 断言,但使用哪个断言框架取决于您。


直接参数

Mokito 使用 equals() 比较直接参数:

verify(mock).mymethod(expectedArg);
// NOTE:   ^ where the parentheses must be closed.

eq匹配器

切勿将 eq 用于单个 arg。使用上述直接参数。 Mokito 使用equals() 比较直接参数 原因: eq 将违反 SonarQube / SonarClound:https://rules.sonarsource.com/java/tag/mockito/RSPEC-6068

argThat 带有多个参数。

如果您使用argThat,则必须为所有参数提供匹配项。例如。如果,在不同的情况下,你有另一个带有 2 个参数的方法:

    verify(mock).mymethod2(eq("VALUE_1"), argThat((x)->false));
    // above is correct as eq() is also an argument matcher.

verify(mock).mymethod2("VALUE_1", argThat((x)-&gt;false));

// above is incorrect; an exception will be thrown, as the first arg. is given without an argument matcher.

地点:

import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

原始问题失败的根本原因是括号的错误位置:

verify(mock.mymethod...。那是错误的。正确的是: verify(mock).*

【讨论】:

这是我最喜欢的答案,比其他答案更有效。 这是如何工作的?验证(模拟).mymethod(eq(“VALUE_1”),argThat((x)->false)); “mymethod”有一个 arg 我们如何发送两个? @max,没错。该示例位于 ... with multiple arguments 部分下,因此,是的,您是对的,它与原始 mymethod(arg0) 案例无关。它只对不同的(2 args)案例有意义。 将其重命名为 mymethod2,以避免混淆,一点点【参考方案4】: 如果您不使用其他匹配器,则不需要 eq 匹配器。 您没有使用正确的语法 - 您的方法调用应该在 .verify(mock) 之外。您现在正在对方法调用的结果进行验证,而不验证任何内容(不进行方法调用)。因此所有测试都通过了。

您的代码应如下所示:

Mockito.verify(mock).mymethod(obj);
Mockito.verify(mock).mymethod(null);
Mockito.verify(mock).mymethod("something_else");

【讨论】:

我之前尝试过,现在再次确定。我还是有同样的问题,测试总是通过。 通过引用验证 @cnexans ,不,它不会通过引用进行验证。此外,eq 将是 SonarQube/SonarCloud 代码气味警报:rules.sonarsource.com/java/tag/mockito/RSPEC-6068【参考方案5】:

我曾以这种方式使用过 Mockito.verify

@UnitTest
public class JUnitServiceTest

    @Mock
    private MyCustomService myCustomService;


    @Test
    public void testVerifyMethod()
    
       Mockito.verify(myCustomService, Mockito.never()).mymethod(parameters); // method will never call (an alternative can be pick to use times(0))
       Mockito.verify(myCustomService, Mockito.times(2)).mymethod(parameters); // method will call for 2 times
       Mockito.verify(myCustomService, Mockito.atLeastOnce()).mymethod(parameters); // method will call atleast 1 time
       Mockito.verify(myCustomService, Mockito.atLeast(2)).mymethod(parameters); // method will call atleast 2 times
       Mockito.verify(myCustomService, Mockito.atMost(3)).mymethod(parameters); // method will call at most 3 times
       Mockito.verify(myCustomService, Mockito.only()).mymethod(parameters); //   no other method called except this
    

【讨论】:

【参考方案6】:

你检查过可模拟类的 equals 方法吗?如果这个总是返回 true 或者你针对同一个实例测试同一个实例并且 equal 方法没有被覆盖(因此只检查引用),那么它返回 true。

【讨论】:

【参考方案7】:

另一种方法是使用 org.mockito.internal.matchers.Equals.Equals 方法而不是重新定义一个:

verify(myMock).myMethod((inputObject)Mockito.argThat(new Equals(inputObjectWanted)));

【讨论】:

【参考方案8】:

您是否尝试过使用相同的匹配器?如:

verify(mockObj).someMethod(same(specificInstance));

我遇到了同样的问题。我尝试使用 eq() 匹配器和 refEq() 匹配器,但我总是误报。当我使用 same() 匹配器时,当参数是不同的实例时测试失败,并且一旦参数是相同的实例就通过了。

【讨论】:

【参考方案9】:

上述许多答案让我感到困惑,但我怀疑这可能是由于旧版本的 Mockito 造成的。这个答案是使用

完成的 Java 11 Mockito 3.1.0 SpringBoot 2.2.7.RELEASE JUnit5

使用 ArgumentCaptor 我已经这样做了:

@Mock
MyClientService myClientService;
@InjectMocks 
MyService myService;


@Test
void myTest() 

  ArgumentCaptor<String> captorParam1 = ArgumentCaptor.forClass(String.class);
  ArgumentCaptor<String> captorParam2 = ArgumentCaptor.forClass(String.class);

  Mockito.when(myClientService.doSomething(captorParam1.capture(), captorParam2.capture(), ArgumentMatchers.anyString()))
      .thenReturn(expectedResponse);

  assertDoesNotThrow(() -> myService.process(data));

  assertEquals("param1", captorParam1.getValue());
  assertEquals("param2", captorParam2.getValue());

  verify(myClientService, times(1))
    .doSomething(anyString(), anyString(), anyString());

【讨论】:

【参考方案10】:
Verify(a).aFunc(eq(b))

在伪代码中:

当在a 实例中时 - 调用名为 aFunc 的函数。

验证这个调用有一个等于b的参数。

【讨论】:

【参考方案11】:

你也可以使用 TypeSafeDiagnosingMatcher

    private Matcher<GetPackagesRequest> expectedPackageRequest(final AvailabilityRequest request) 
    return new TypeSafeDiagnosingMatcher<GetPackagesRequest>() 

        StringBuilder text = new StringBuilder(500);

        @Override
        protected boolean matchesSafely(GetPackagesRequest req, Description desc) 
            String productCode = req.getPackageIds().iterator().next().getValue();
            if (productCode.equals(request.getSupplierProductCode())) 
                text.append("ProductCode not equal! " + productCode + " , " + request.getSupplierProductCode());
                return true;
            

            text.append(req.toString());
            return false;
        

        @Override
        public void describeTo(Description d) 
            d.appendText(text.toString());
        
    ;

然后验证调用:

Mockito.verify(client).getPackages(Mockito.argThat(expectedPackageRequest(request)));

【讨论】:

以上是关于莫基托。验证方法参数的主要内容,如果未能解决你的问题,请参考以下文章

spring方法验证中如何验证方法参数默认为NotNull?

验证方法体中是不是使用了方法参数

验证 IDbcommand 参数的最佳方法

Laravel 5.7 - 覆盖请求验证类中的 all() 方法以验证路由参数?

spring 方法验证参数

如何使用 JSR 验证在 Spring 中验证 Controller 方法参数?