使用 mockito 验证对象属性值

Posted

技术标签:

【中文标题】使用 mockito 验证对象属性值【英文标题】:Verify object attribute value with mockito 【发布时间】:2010-11-11 16:27:57 【问题描述】:

我有一个方法调用,我想用 mockito 模拟。首先,我创建并注入了一个对象实例,将在该实例上调用该方法。我的目标是验证方法调用中的对象之一。

mockito 是否允许您在调用 mock 方法时断言或验证对象及其属性?

例子

Mockito.verify(mockedObject)
       .someMethodOnMockedObject(
              Mockito.<SomeObjectAsArgument>anyObject())

我不想做anyObject(),而是想检查参数对象是否包含一些特定字段

Mockito.verify(mockedObject)
       .someMethodOnMockedObject(
              Mockito.<SomeObjectAsArgument>**compareWithThisObject()**)

【问题讨论】:

作为在这些情况下使用 mockito 的替代方案,您可以考虑创建一个自定义存根来扩展 mockedObject 的类,并覆盖 someMethodOnMockedObject 以保存对象以供以后比较。 【参考方案1】:

添加到 Mockito 的新功能使这变得更加容易,

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

看看 Mockito documentation


如果有多个参数,并且只需要捕获单个参数,请使用其他 ArgumentMatchers 包装其余参数:

verify(mock).doSomething(eq(someValue), eq(someOtherValue), argument.capture());
assertEquals("John", argument.getValue().getName());

【讨论】:

如果你的方法有多个参数,你也必须对所有其他参数使用匹配器。 akcasoy.wordpress.com/tag/argumentcaptor 如果有多个参数怎么办?您如何指定您感兴趣的确切内容? @IgorGanapolsky 假设你需要做的 doSomething 的第二个字符串参数:verify(mock).doSomething(argument.capture(), anyString()); 对所有参数使用匹配器的需要完全符合标准的全或无匹配器使用规范。【参考方案2】:

我认为验证参数对象的最简单方法是使用refEq 方法:

Mockito.verify(mockedObject).someMethodOnMockedObject(ArgumentMatchers.refEq(objectToCompareWith));

即使对象没有实现equals()也可以使用,因为使用了反射。如果您不想比较某些字段,只需将它们的名称添加为 refEq 的参数即可。

【讨论】:

这是一种非常优雅的方式,但不幸的是 org.mockito.Matchers 现在已弃用 @ihebiheb 已移至 ArgumentMatchers 没有看到这如何远程回答比较字段的问题:“我想检查参数对象是否包含一些特定字段” 当您需要匹配未实现equals() 的参数时,此解决方案非常有用尤其是。我试图找到一个专门关于该用例的问题/答案,但奇怪的是没有找到。然而我很犹豫是否发布一个关于这个的自我回答的问题,因为我很确定某处肯定有重复...... @oligofren refEq 将通过反射检查所有字段,因此它会自动覆盖“特定字段”。此外,如果您不需要所有这些,您可以排除一些。它没有真正涵盖的唯一停止是当您的白名单比黑名单短时,例如,您只想测试多个字段中的几个。【参考方案3】:

如果您不想使用ArgumentCaptor(例如,因为您也在使用存根),另一种可能性是结合使用 Hamcrest Matchers 和 Mockito。

import org.mockito.Mockito
import org.hamcrest.Matchers
...

Mockito.verify(mockedObject).someMethodOnMockedObject(MockitoHamcrest.argThat(
    Matchers.<SomeObjectAsArgument>hasProperty("propertyName", desiredValue)));

【讨论】:

旁注:确保Matchers 包是正确的,因为用org.mockito.Matchers 类编写同一行代码会引发误导性异常,指出模拟函数的参数根本不匹配。 请注意,在现代 Mockito 版本中,它是 MockitoHamcrest.argThat() 而不是 Mockito.argThat()【参考方案4】:

这是基于 answer from iraSenthil 但带有注释 (Captor) 的答案。在我看来,它有一些优势:

它更短 更容易阅读 它可以在没有警告的情况下处理泛型

例子:

@RunWith(MockitoJUnitRunner.class)
public class SomeTest

    @Captor
    private ArgumentCaptor<List<SomeType>> captor;

    //...

    @Test 
    public void shouldTestArgsVals() 
        //...
        verify(mockedObject).someMethodOnMockedObject(captor.capture());

        assertThat(captor.getValue().getXXX(), is("expected"));
    

【讨论】:

这仅适用于 params 中的单个参数。 您可以将一个俘虏用于多个论点。如果您捕获多个参数,您可以使用captor.getAllValues() 列出所有结果。答案中使用的方法captor.getValue() 提供最后一个结果。【参考方案5】:

如果您使用的是 Java 8,则可以使用 Lambda 表达式进行匹配。

import java.util.Optional;
import java.util.function.Predicate;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;

public class LambdaMatcher<T> extends BaseMatcher<T>

    private final Predicate<T> matcher;
    private final Optional<String> description;

    public LambdaMatcher(Predicate<T> matcher)
    
        this(matcher, null);
    

    public LambdaMatcher(Predicate<T> matcher, String description)
    
        this.matcher = matcher;
        this.description = Optional.ofNullable(description);
    

    @SuppressWarnings("unchecked")
    @Override
    public boolean matches(Object argument)
    
        return matcher.test((T) argument);
    

    @Override
    public void describeTo(Description description)
    
        this.description.ifPresent(description::appendText);
    

示例调用

@Test
public void canFindEmployee()

    Employee employee = new Employee("John");
    company.addEmployee(employee);

    verify(mockedDal).registerEmployee(argThat(new LambdaMatcher<>(e -> e.getName()
                                                                         .equals(employee.getName()))));

更多信息:http://source.coveo.com/2014/10/01/java8-mockito/

【讨论】:

【参考方案6】:

一种简化的解决方案,无需创建新的 Matcher 实现类并使用 lambda 表达式:

verify(mockObject).someMockMethod(
        argThat((SomeArgument arg) -> arg.fieldToMatch.equals(expectedFieldValue)));

【讨论】:

这里缺少右括号,但我无法编辑单个字符更改。 我现在添加了缺少的括号【参考方案7】:

上面的解决方案在我的情况下并没有真正起作用。我无法使用 ArgumentCaptor,因为该方法被多次调用,我需要验证每一个。一个带有“argThat”的简单匹配器很容易做到这一点。

自定义匹配器

// custom matcher
private class PolygonMatcher extends ArgumentMatcher<PolygonOptions> 
    private int fillColor;
    public PolygonMatcher(int fillColor) 
        this.fillColor = fillColor;
    

    @Override
    public boolean matches(Object argument) 
        if (!(argument instanceof PolygonOptions)) return false;
        PolygonOptions arg = (PolygonOptions)argument;
        return Color.red(arg.getFillColor()) == Color.red(fillColor)
                && Color.green(arg.getFillColor()) == Color.green(fillColor)
                && Color.blue(arg.getFillColor()) == Color.blue(fillColor);
    

测试运行者

// do setup work setup
// 3 light green polygons
int green = getContext().getResources().getColor(R.color.dmb_rx_bucket1);
verify(map, times(3)).addPolygon(argThat(new PolygonMatcher(green)));

// 1 medium yellow polygons
int yellow = getContext().getResources().getColor(R.color.dmb_rx_bucket4);
    verify(map, times(1)).addPolygon(argThat(new PolygonMatcher(yellow)));

// 3 red polygons
int orange = getContext().getResources().getColor(R.color.dmb_rx_bucket5);
verify(map, times(3)).addPolygon(argThat(new PolygonMatcher(orange)));

// 2 red polygons
int red = getContext().getResources().getColor(R.color.dmb_rx_bucket7);
verify(map, times(2)).addPolygon(argThat(new PolygonMatcher(red)));

【讨论】:

【参考方案8】:

来自com.nhaarman.mockito_kotlin 的 koltin 非常漂亮和干净的解决方案

verify(mock).execute(argThat 
    this.param = expected
)

【讨论】:

【参考方案9】:

您可以参考以下内容:

Mockito.verify(mockedObject).someMethodOnMockedObject(eq(desiredObject))

这将验证是否以desiredObject为参数调用了mockedObject的方法。

【讨论】:

【参考方案10】:

另一种简单的方法:

import org.mockito.BDDMockito;    
import static org.mockito.Matchers.argThat;
import org.mockito.ArgumentMatcher;

BDDMockito.verify(mockedObject)
        .someMethodOnMockedObject(argThat(new ArgumentMatcher<TypeOfMethodArg>() 

            @Override
            public boolean matches(Object argument) 
                final TypeOfMethodArg castedArg = (TypeOfMethodArg) argument;

                // Make your verifications and return a boolean to say if it matches or not
                boolean isArgMarching = true;

                return isArgMarching;
            
        ));

【讨论】:

【参考方案11】:

refEq 的 javadoc 提到相等性检查很浅!您可以在下面的链接中找到更多详细信息:

https://static.javadoc.io/org.mockito/mockito-core/2.2.29/org/mockito/ArgumentMatchers.html#refEq(T,%20java.lang.String...)

当您使用其他未实现 .equals() 方法的类时,无法控制“浅相等”问题,“DefaultMongoTypeMapper”类是未实现 .equals() 方法的示例。

org.springframework.beans.factory.support 提供了一种可以生成 bean 定义而不是创建对象实例的方法,它可以用来消除比较失败。

 genericBeanDefinition(DefaultMongoTypeMapper.class)
                        .setScope(SCOPE_SINGLETON)
                        .setAutowireMode(AUTOWIRE_CONSTRUCTOR)
                        .setLazyInit(false)
                        .addConstructorArgValue(null)
                        .getBeanDefinition()

**"bean定义只是对bean的描述,不是bean本身。 bean 描述正确地实现了 equals() 和 hashCode(),因此我们提供了一个定义来告诉 spring 它应该如何创建一个,而不是创建一个新的 DefaultMongoTypeMapper()"

在你的例子中,你可以这样做

Mockito.verify(mockedObject)
       .doSoething(genericBeanDefinition(YourClass.class).setA("a")
       .getBeanDefinition());

【讨论】:

以上是关于使用 mockito 验证对象属性值的主要内容,如果未能解决你的问题,请参考以下文章

Mockito - 验证对象根本没有被调用

Android单元测试之Mockito的使用

学习笔记单元测试之mockito学习笔记

学习笔记单元测试之mockito学习笔记

学习笔记单元测试之mockito学习笔记

如何用mockito+spring进行单元测试