Mockito/JMockit & Hamcrest 匹配器:如何验证 Lists/Collections?
Mockito/JMockit & Hamcrest matchers : How to verify Lists/Collections?
此 2013 post on SO 询问如何使用 Hamcrest 匹配器验证 Mockito 中的 lists/collections 调用。公认的解决方案是将 Matcher 转换为 (Collection)。
我正在尝试做类似的事情,但是 运行 变成了 class 转换错误。我不确定我是否在滥用 Hamcrest 匹配器,或者 Mockito 是否不支持这种用法。就我而言,我正在尝试使用匹配器列表作为我的参数:
static class Collaborator
{
void doSomething(Iterable<String> values) {}
}
@Test
public void usingMockito()
{
Collaborator mock = Mockito.mock(Collaborator.class);
mock.doSomething(Arrays.asList("a", "b"));
// legal cast
Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains("a", "b")));
// legal cast
Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Matchers.equalTo("a"), Matchers.equalTo("b"))));
// illegal cast!!! Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>
Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")))));
}
但是我得到了转换错误:
Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>
我在做一些不受支持的事情吗?
我认为这是由于 Hamcrest 中令人讨厌的歧义造成的,它在 Matchers class:
<E> Matcher<Iterable<? extends E>> contains(E... items)
<E> Matcher<Iterable<? extends E>> contains(Matcher<? super E> itemMatcher)
<E> Matcher<Iterable<? extends E>> contains(Matcher<? super E>... itemMatchers)
<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)
没错,根据您向 Hamcrest 传递的是项目、匹配器、匹配器的可变参数数组还是匹配器列表,您会得到不同的行为。因为 Java 不反对 Hamcrest 匹配器的匹配列表,所以一个语句有很多机会匹配多个重载,并且它们之间的选择是最具体的重载,由令人眼花缭乱的类型代数决定JLS 18.5.4.
我认为您打算在上面的第 4 项中传递 contains
Matcher<E>
(Matcher<String>
) 的列表并取回 Matcher<Iterable<? extends String>>
,但是编译器将其视为#1——传递 contains
类型为 E
(List<Matcher<String>>
) 的 value 并取回 Matcher<Iterable<? extends List<Matcher<String>>>>
.
有几个解决方法,我还没有测试过:
将 Matcher
提取到一个变量,您可以使用像 contains
这样的 Hamcrest 匹配器来做,但不能像 argThat
:
这样的 Mockito 匹配器
Matcher<Iterable<String>> matchesAAndB = Matchers.contains(
Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")));
Mockito.verify(mock).doSomething((Collection<String>)argThat(matchesAAndB));
明确选择 E:
Mockito.verify(mock).doSomething((Collection<String>)argThat(
Matchers.<String>contains(Arrays.asList(
Matchers.equalTo("a"), Matchers.equalTo("b")))));
正如 Jeff Bowman 已经指出的那样,问题在于编译器不知道您尝试调用 4 个 contains
方法中的哪一个。
您正在构建的列表
Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))
属于
类型
List<Matcher<String>>
但是您要调用的 contains
方法 (<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)
) 需要类型
List<Matcher<? super String>>
作为参数。由于您的列表类型与预期的不匹配,编译器实际上认为您正在尝试调用
<E> Matcher<Iterable<? extends E>> contains(E... items)
解决方案:给编译器它想要的。创建 List<Matcher<? super String>>
而不是 List<Matcher<String>>
:
List<Matcher<? super String>> matchersList = new ArrayList<>();
matchersList.add(Matchers.equalTo("a"));
matchersList.add(Matchers.equalTo("b"));
// no illegal cast anymore
Mockito.verify(mock).doSomething(
(Collection<String>) argThat(Matchers.contains(matchersList)));
编辑:
从他的评论中添加 Jeff Bowman 的内联解决方案,这样就可以使用问题中所述的 Arrays.asList
:
Mockito.verify(mock).doSomething(
(Collection<String>) argThat(
Matchers.contains(
Arrays.<Matcher<? super String>> asList(
Matchers.equalTo("a"), Matchers.equalTo("b")
)
)
)
);
最好的方法是使用标准 assertThat
方法(来自 Hamcrest 或 JUnit),该方法最适用于任何 Hamcrest 匹配器。使用 JMockit,您可以执行以下操作:
@Test
public void usingJMockit(@Mocked final Collaborator mock) {
mock.doSomething(asList("a", "b"));
new Verifications() {{
List<String> values;
mock.doSomething(values = withCapture());
// Now check the list of captured values using JUnit/Hamcrest:
assertThat(values, contains("a", "b"));
// Alternatively, could have used Asser4J, FEST Assert, etc.
}};
}
我更愿意使用 allOf
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
...
Mockito.verify(mock).doSomething(
argThat(
allOf(
hasItems(equalTo("a")),
hasItems(equalTo("b"))
)
)
);
此 2013 post on SO 询问如何使用 Hamcrest 匹配器验证 Mockito 中的 lists/collections 调用。公认的解决方案是将 Matcher 转换为 (Collection)。
我正在尝试做类似的事情,但是 运行 变成了 class 转换错误。我不确定我是否在滥用 Hamcrest 匹配器,或者 Mockito 是否不支持这种用法。就我而言,我正在尝试使用匹配器列表作为我的参数:
static class Collaborator
{
void doSomething(Iterable<String> values) {}
}
@Test
public void usingMockito()
{
Collaborator mock = Mockito.mock(Collaborator.class);
mock.doSomething(Arrays.asList("a", "b"));
// legal cast
Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains("a", "b")));
// legal cast
Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Matchers.equalTo("a"), Matchers.equalTo("b"))));
// illegal cast!!! Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>
Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")))));
}
但是我得到了转换错误:
Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>
我在做一些不受支持的事情吗?
我认为这是由于 Hamcrest 中令人讨厌的歧义造成的,它在 Matchers class:
<E> Matcher<Iterable<? extends E>> contains(E... items)
<E> Matcher<Iterable<? extends E>> contains(Matcher<? super E> itemMatcher)
<E> Matcher<Iterable<? extends E>> contains(Matcher<? super E>... itemMatchers)
<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)
没错,根据您向 Hamcrest 传递的是项目、匹配器、匹配器的可变参数数组还是匹配器列表,您会得到不同的行为。因为 Java 不反对 Hamcrest 匹配器的匹配列表,所以一个语句有很多机会匹配多个重载,并且它们之间的选择是最具体的重载,由令人眼花缭乱的类型代数决定JLS 18.5.4.
我认为您打算在上面的第 4 项中传递 contains
Matcher<E>
(Matcher<String>
) 的列表并取回 Matcher<Iterable<? extends String>>
,但是编译器将其视为#1——传递 contains
类型为 E
(List<Matcher<String>>
) 的 value 并取回 Matcher<Iterable<? extends List<Matcher<String>>>>
.
有几个解决方法,我还没有测试过:
将
这样的 Mockito 匹配器Matcher
提取到一个变量,您可以使用像contains
这样的 Hamcrest 匹配器来做,但不能像argThat
:Matcher<Iterable<String>> matchesAAndB = Matchers.contains( Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))); Mockito.verify(mock).doSomething((Collection<String>)argThat(matchesAAndB));
明确选择 E:
Mockito.verify(mock).doSomething((Collection<String>)argThat( Matchers.<String>contains(Arrays.asList( Matchers.equalTo("a"), Matchers.equalTo("b")))));
正如 Jeff Bowman 已经指出的那样,问题在于编译器不知道您尝试调用 4 个 contains
方法中的哪一个。
您正在构建的列表
Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))
属于
类型List<Matcher<String>>
但是您要调用的 contains
方法 (<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)
) 需要类型
List<Matcher<? super String>>
作为参数。由于您的列表类型与预期的不匹配,编译器实际上认为您正在尝试调用
<E> Matcher<Iterable<? extends E>> contains(E... items)
解决方案:给编译器它想要的。创建 List<Matcher<? super String>>
而不是 List<Matcher<String>>
:
List<Matcher<? super String>> matchersList = new ArrayList<>();
matchersList.add(Matchers.equalTo("a"));
matchersList.add(Matchers.equalTo("b"));
// no illegal cast anymore
Mockito.verify(mock).doSomething(
(Collection<String>) argThat(Matchers.contains(matchersList)));
编辑:
从他的评论中添加 Jeff Bowman 的内联解决方案,这样就可以使用问题中所述的 Arrays.asList
:
Mockito.verify(mock).doSomething(
(Collection<String>) argThat(
Matchers.contains(
Arrays.<Matcher<? super String>> asList(
Matchers.equalTo("a"), Matchers.equalTo("b")
)
)
)
);
最好的方法是使用标准 assertThat
方法(来自 Hamcrest 或 JUnit),该方法最适用于任何 Hamcrest 匹配器。使用 JMockit,您可以执行以下操作:
@Test
public void usingJMockit(@Mocked final Collaborator mock) {
mock.doSomething(asList("a", "b"));
new Verifications() {{
List<String> values;
mock.doSomething(values = withCapture());
// Now check the list of captured values using JUnit/Hamcrest:
assertThat(values, contains("a", "b"));
// Alternatively, could have used Asser4J, FEST Assert, etc.
}};
}
我更愿意使用 allOf
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
...
Mockito.verify(mock).doSomething(
argThat(
allOf(
hasItems(equalTo("a")),
hasItems(equalTo("b"))
)
)
);