我有一个方法调用,我想用 mockito 模拟。首先,我创建并注入了一个对象实例,将在该实例上调用该方法。我的目标是验证方法调用中的对象之一。
有没有一种方法可以让你在调用 mock 方法时断言或验证对象及其属性?
例子
Mockito.verify(mockedObject)
.someMethodOnMockedObject(
Mockito.<SomeObjectAsArgument>anyObject())
而不是做 anyObject()
我想检查参数对象是否包含一些特定的字段
Mockito.verify(mockedObject)
.someMethodOnMockedObject(
Mockito.<SomeObjectAsArgument>**compareWithThisObject()**)
添加到 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());
我认为验证参数对象的最简单方法是使用 refEq
方法:
Mockito.verify(mockedObject).someMethodOnMockedObject(ArgumentMatchers.refEq(objectToCompareWith));
即使对象没有实现 equals()
也可以使用它,因为使用了反射。如果您不想比较某些字段,只需将它们的名称添加为 refEq
的参数。
equals()
的参数时,此解决方案非常有用。尤其是。我试图找到一个专门关于该用例的问题/答案,但奇怪的是没有找到。然而,我很犹豫是否要发布一个关于此的自我回答问题,因为我很确定某处肯定有重复......
refEq
将通过反射检查所有字段,因此它会自动覆盖“特定字段”。此外,如果您不需要所有这些,您可以排除一些。它没有真正涵盖的唯一停止是当您的白名单比黑名单短时,例如,您只想测试许多字段中的几个字段。
如果您不想使用 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
类编写同一行代码会引发误导性异常,指出模拟函数的参数根本不匹配。
MockitoHamcrest.argThat()
而不是 Mockito.argThat()
这是基于 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"));
}
}
captor.getAllValues()
列出所有结果。回答中使用的方法 captor.getValue()
提供最后一个结果。
如果您使用的是 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/
一个简化的解决方案,无需创建新的 Matcher 实现类并使用 lambda 表达式:
verify(mockObject).someMockMethod(
argThat((SomeArgument arg) -> arg.fieldToMatch.equals(expectedFieldValue)));
上面的解决方案在我的情况下并没有真正起作用。我无法使用 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)));
com.nhaarman.mockito_kotlin
在 koltin 中提供了非常好的和干净的解决方案
verify(mock).execute(argThat {
this.param = expected
})
您可以参考以下内容:
Mockito.verify(mockedObject).someMethodOnMockedObject(eq(desiredObject))
这将验证是否以 desiredObject 作为参数调用了 mockedObject 的方法。
另一种简单的方法:
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;
}
}));
refEq 的 javadoc 提到相等性检查很浅!您可以在以下链接中找到更多详细信息:
当您使用其他未实现 .equals() 方法的类时,无法控制“浅相等”问题,“DefaultMongoTypeMapper”类是未实现 .equals() 方法的示例。
org.springframework.beans.factory.support 提供了一种可以生成 bean 定义而不是创建对象实例的方法,它可以用来 git 摆脱比较失败。
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());