java 使用 Mockito 通过反射模拟方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15970810/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Mockito to mock methods by reflection
提问by Arno Jost
We are using a Mock-Factory to give our developers the most possible comfort about mocking functionality with the less possible needed know-how about mockito itself.
我们正在使用 Mock-Factory 为我们的开发人员提供关于模拟功能的最大可能的舒适度,同时尽可能少地需要关于 mockito 本身的知识。
To do so, our Mock-Factory is providing a method to create a mock given the class-name, the method-name (by regexp) and the given return value which looks about the following (cleand up to the relevant parts for this question):
为此,我们的 Mock-Factory 提供了一种方法来创建一个给定类名、方法名(通过正则表达式)和给定返回值的模拟,它看起来如下(清理到这个问题的相关部分):
public <T> T getMockForMethod(Class<T> clazz, String methodName, Object methodResponse)
{
T mockForMethod = mock(clazz);
for (Method m : clazz.getDeclaredMethods ())
{
if (m.getName ().matches (methodName) &&
m.getReturnType ().isAssignableFrom (methodResponse.getClass ()))
{
try
{
Class<?>[] paramTypes = m.getParameterTypes ();
Object[] params = new Object[paramTypes.length];
for (Object o : params)
{
o = Mockito.anyObject ();
}
Mockito.when (m.invoke (mockForService, params)).thenReturn (methodResponse);
}
catch (IllegalArgumentException e)
{
e.printStackTrace (System.err);
}
catch (IllegalAccessException e)
{
e.printStackTrace (System.err);
}
catch (InvocationTargetException e)
{
e.printStackTrace (System.err);
}
}
}
return mockForMethod;
}
As u can see the method name is matched by name (regexp) and the correct given return type.
如您所见,方法名称与名称(regexp)和正确的给定返回类型匹配。
It works fine, but i'm a bit bothered about the fact that i have to build the artificial parameter-array params
!
And no, the approach
它工作正常,但我对我必须构建人工参数数组的事实感到有些困扰params
!不,方法
Mockito.when (m.invoke (mockForService, Mockito.anyVararg ())).thenReturn(methodResponse);
didn't work! But i don't really understand why!?
没用!但我真的不明白为什么!?
Can anyone give me the reason or a better alternative to the code above?
谁能给我上面代码的原因或更好的替代方案?
回答by JB Nizet
You shouldn't do this. Mockito is a really well-designed, simple to learn, extremely well-documented, and almost de-facto standard framework. And it's type-safe and doesn't need reflection, which makes tests easy to read and understand.
你不应该这样做。Mockito 是一个设计精良、易于学习、文档齐全且几乎是事实上的标准框架。而且它是类型安全的,不需要反射,这使得测试易于阅读和理解。
Let your developers learn the real Mockito and use its API directly. They'll be happy to use it because it will have a better, easier to use and more flexible design than your own super-api, and they'll know that they won't learn Mockito for nothing because they'll probably use it in other projects or even other jobs.
让您的开发人员学习真正的 Mockito 并直接使用其 API。他们会很乐意使用它,因为它比您自己的超级 api 具有更好、更易于使用和更灵活的设计,并且他们会知道他们不会无缘无故地学习 Mockito,因为他们可能会使用它在其他项目甚至其他工作中。
Mockito doesn't need another proprietary API on top of it. My suggested alternative is thus to forget about this and teach Mockito to your developers.
Mockito 不需要其他专有 API。因此,我建议的替代方案是忘记这一点,并将 Mockito 教给您的开发人员。
回答by Brice
Well your approach isn't really a good one, its typically over-engineering developers candyland. Even if your team are "younglings" it's not like they have to write ASM when using Mockito. Plus if you go this way, you avoid all the benefit in simplicity, expressiveness or plugability that Mockito provides. As an architect I would rather make sure my engineers understand what they are doing rather than put them in a baby park. How can they become a great team otherwise ?
好吧,您的方法并不是真正的好方法,它通常是过度设计开发人员的糖果乐园。即使您的团队是“年轻人”,他们也不必在使用 Mockito 时编写 ASM。另外,如果你这样做,你就避免了 Mockito 提供的简单性、表现力或可插入性方面的所有好处。作为一名建筑师,我宁愿确保我的工程师了解他们在做什么,而不是把他们放在婴儿公园里。否则他们怎么能成为一支伟大的球队?
Also the implementation provided here is probably way too simplistic to support all the cases you can have when dealing with reflection, bridge methods, varargs, overriding, etc. It doesn't have understandable message if this piece of code fails. In short you loose all the benefit of using Mockito directly, and add unnecessary to the project anyway.
此外,此处提供的实现可能过于简单,无法支持您在处理反射、桥接方法、可变参数、覆盖等时可能遇到的所有情况。如果这段代码失败,则无法理解消息。简而言之,您失去了直接使用 Mockito 的所有好处,并且无论如何都将不必要的添加到项目中。
EDIT: Just saw the answer of JB Nizet, I agree completely with him.
But however for the sake of answering your question, what is happening with there. Given a short look at your code, it seems that you don't want to care about the args passed to the method.
但是,为了回答您的问题,那里发生了什么。简单看一下您的代码,您似乎不想关心传递给方法的参数。
So suppose you have the following real method in the class being mocked :
因此,假设您在被模拟的类中有以下真实方法:
String log2(String arg1, String arg2)
and
和
String log1N(String arg1, String... argn)
Now what the compiler sees, a first method log2
that takes 2 parameter of type String
and a method log1N
that takes 2 parameter, one of type String
and the other one of type String[]
(variable arguments are transformed by the compiler to an array).
现在编译器看到的是,第一个方法log2
接受 2 个类型的参数,一个String
方法log1N
接受 2 个参数,一个是类型String
,另一个是类型String[]
(变量参数被编译器转换为数组)。
If using Mockito directly on those method you will write the following.
如果直接在这些方法上使用 Mockito,您将编写以下内容。
given(mock.log2("a", "b")).will(...);
given(mock.log1N("a", "b", "c", "d")).will(...);
You write logN("a", "b", "c", "d")
just like plain java. And when you want to use argument matchers you will write this with the 2 arg method:
你写的logN("a", "b", "c", "d")
就像普通的java一样。当您想使用参数匹配器时,您将使用 2 arg 方法编写它:
given(mock.log2(anyString(), anyString())).will(...);
And now with the vararg method :
现在使用 vararg 方法:
given(mock.log1N(anyString(), anyString(), anyString())).will(...); // with standard arg matchers
given(mock.log1N(anyString(), Mockito.<String>anyVararg())).will(...); // with var arg matcher
In the first case Mockito is smart enough to understand that the last two argument matchers, must go in the last vararg, i.e. argn
, so Mockito understand this method will matches if there is only 3 arguments (varargs being flatened)
In the second case anyVararg
indicates to mockito, there could be any count of arguments.
在第一种情况下,Mockito 足够聪明,可以理解最后两个参数匹配器必须在最后一个 vararg 中,即argn
,因此 Mockito 理解如果只有 3 个参数(varargs 被扁平化),则此方法将匹配在第二种情况下anyVararg
表示mockito,可以有任何数量的参数。
Now, going back to the reflection code, the signature of Method.invoke
is :
现在,回到反射代码,的签名Method.invoke
是:
public Object invoke(Object obj, Object... args)
Typical usage with reflection and varargs when passing real arguments would be :
传递实际参数时,反射和可变参数的典型用法是:
log2_method.invoke(mock, "a", "b");
log1N_method.invoke(mock, "a", new String[] { "b", "c", "d" });
or as this invoke method is based on vararg it could be written like this :
或者因为这个调用方法是基于 vararg 的,它可以这样写:
log1N_method.invoke(mock, new Object[] {"a", new String[] { "b", "c", "d" }});
So the passed argument vararg array in invoke, must actually matches the signature of the called method.
因此 invoke 中传递的参数 vararg 数组必须实际匹配被调用方法的签名。
This call will fail of course then log1N_method.invoke(mock, "a", "b", "c", "d");
这个调用当然会失败 log1N_method.invoke(mock, "a", "b", "c", "d");
So when you tried this line of code with anyVararg
, the invocation wasn't respecting the signature of the called method arguments:
因此,当您使用 尝试这行代码时anyVararg
,调用不尊重被调用方法参数的签名:
Mockito.when (m.invoke(mockForMethod, Mockito.anyVararg())).thenReturn(methodResponse);
It would only work if the method m
had one argument only. And yet you would have to make it to the reflection API that's inside an array (because vararg are actually arrays). The trick here is that the vararg in invoke(Object obj, Object... args)
is confusing with the called method vararg.
只有当该方法m
只有一个参数时它才会起作用。然而,您必须使用数组内的反射 API(因为 vararg 实际上是数组)。这里的技巧是 vararg ininvoke(Object obj, Object... args)
与被调用的方法 vararg 混淆。
So using arg matchers with my example you should do that way :
因此,在我的示例中使用 arg 匹配器,您应该这样做:
when(
log1N.invoke(mock, anyString(), new String[] { Mockito.<String>anyVararg() })
).thenReturn("yay");
So if there is only one argument that is a vararg, it's the same thing:
因此,如果只有一个参数是可变参数,则是相同的:
String log1(String... argn)
when(
logN.invoke(mock, new String[] { Mockito.<String>anyVararg() })
).thenReturn("yay");
And of course you cannot use anyVararg
on a non vararg method, because the argument layout in the signature won't match.
当然,您不能anyVararg
在非可变参数方法上使用,因为签名中的参数布局不匹配。
As you see here, if you go this way of abstracting Mockito to your team, you will have to manage a lot of class level oddities. I'm not saying this is impossible. But as an owner of this code you'll have to maintain it, fix it, and take into account may things that could go wrong, and make it understandable to the users of this abstractioncode.
正如您在此处看到的,如果您采用这种将 Mockito 抽象到您的团队的方式,您将不得不管理许多类级别的奇怪问题。我并不是说这是不可能的。但是作为此代码的所有者,您必须维护它、修复它并考虑可能出错的事情,并使此抽象代码的用户可以理解它。
Sorry to feel so pushy, that seem so wrong to me that I stresses these warnings.
很抱歉感到如此咄咄逼人,这对我来说似乎是错误的,所以我强调了这些警告。
回答by Jeff Bowman
I agree with JB Nizet that you should just allow your developers to use the native API.
我同意 JB Nizet 的观点,即您应该只允许您的开发人员使用本机 API。
However, if you do need to provide a default answer for a large number of methods matched by a regular expression, and you can't or won't fix the overweight interface that that implies, then you can use this SO answeras inspiration to make this safer Mockito refactoring using default Answers:
但是,如果您确实需要为正则表达式匹配的大量方法提供默认答案,并且您不能或不会修复所暗示的超重接口,那么您可以使用此 SO 答案作为灵感使用默认答案使这个更安全的 Mockito 重构:
@Test public void yourTest() {
YourClass yourClass = mock(YourClass.class, new DefaultAnswer("foo.*Bar", baz));
when(yourClass.someOtherMethod()).thenReturn("Some custom result");
/* test */
}
private class DefaultAnswer implements Answer<Object> {
private final String methodRegex; // or save a Pattern object instead
private final Object returnValue;
DefaultAnswer(String methodRegex, Object returnValue) { /* set fields */ }
@Override public Object answer(InvocationOnMock invocation) throws Throwable {
if (invocation.getMethod().getName().matches(methodRegex)) {
return returnValue;
} else {
return Mockito.RETURNS_DEFAULTS.answer(invocation);
}
}
}