我在 Mockito 2.2 中使用什么代替 Whitebox 来设置字段?
在使用 Mockito 1.9.x 时,我一直在使用 Whitebox
将字段值设置为注入"模拟.请参阅下面的示例:
@Before公共无效设置(){eventHandler = new ProcessEventHandler();securityService = new SecurityServiceMock();注册服务 = 模拟(注册服务.class);Whitebox.setInternalState(eventHandler, "registrationService", registrationService);Whitebox.setInternalState(eventHandler, "securityService", securityService);}
我真的很喜欢这种方法,但现在我尝试升级到 Mockito
2.2.7
我注意到(或者更确切地说,我的 IDE 注意到并告诉了我很多次),在 Mockito 中不再可以找到 Whitebox.
我找到了一种替代方案,可以作为替代品,那就是 org.powermock.reflect.Whitebox
,问题是我得到了另一个依赖项(Powermock),只是为了使用 Whitebox.
Powermock
也有一个名为 Whitebox
的类,但不幸的是它看起来好像不能与 Mockito 2.2.x
一起使用p>
既然 Whitebox
不再可用,我可以使用 Mockito 中的任何好的替代方法来手动注入"字段吗?
解决方案
我在评论中回复@JeffBowman 的帖子.简而言之,我选择复制 WhiteBox 的代码并使用它,因为它在大多数测试用例中使用,并且该类与其他类没有依赖关系.这是解决此问题的最快途径.
注意 @bcody 建议的解决方案是一个更好的选择,如果您使用的是 spring,它不需要额外的代码供您维护.我很晚才得到这些信息:(
解决方案请注意,Whitebox
始终位于 org.mockito.internal
包中.除了主要版本号的递增之外,internal
名称表明该软件包可能会受到重大更改.
如果您确实想在测试中设置其他无法访问的字段,您可以像 setInternalState
一样进行设置,这只是在层次结构,在其上调用 setAccessible
,然后设置它.完整代码在 grepcode 上. 您还可以查看许多 在测试中设置不可访问状态的其他方法.
public static void setInternalState(Object target, String field, Object value) {类<?>c = 目标.getClass();尝试 {字段 f = getFieldFromHierarchy(c, field);//检查超类.f.setAccessible(true);f.set(目标,值);} 捕捉(异常 e){抛出新的运行时异常(无法在私有字段上设置内部状态.[...]",e);}}
然而,在这种情况下,我的一般建议是停止使用工具:Java 的四个封装级别(公共、受保护、包、私有)不是必须足够细化以表达您尝试表达的保护程度,并且通常更容易添加一个记录良好的初始化方法或构造函数覆盖来覆盖依赖关系,因为您正在尝试进行反思.如果您将测试与其测试的类放在同一个 Java 包中,您通常甚至可以将字段或方法/构造函数设置为包私有,这也是设置并行源文件夹 src
和 tests
(etc) 代表同一个 Java 包的两半.
虽然有些人将这种额外的方法或构造函数视为API 污染",但我认为它是针对您的类中最重要的消费者之一(它自己的测试)的要求进行的编码.如果您需要一个原始的外部接口,您可以轻松地单独定义一个,这样您就可以隐藏您想要的任何细节.但是,您可能会发现您喜欢将任何真实或模拟实现直接注入到您现在更灵活的组件中的能力,此时您可能需要研究依赖注入模式或框架.p>
When using Mockito 1.9.x I have been using Whitebox
to set values of fields to "inject" mocks. Se example below:
@Before
public void setUp() {
eventHandler = new ProcessEventHandler();
securityService = new SecurityServiceMock();
registrationService = mock(RegistrationService.class);
Whitebox.setInternalState(eventHandler, "registrationService", registrationService);
Whitebox.setInternalState(eventHandler, "securityService", securityService);
}
I really like this approach, but now that I tried to upgrade to Mockito
2.2.7
I noticed (or rather, my IDE noticed and told me quite a few times) that Whitebox was no longer to be found in Mockito.
I have found one alternative, that can work as a replacement, and that is org.powermock.reflect.Whitebox
, the problem with that is that I get another dependency (Powermock), just to use Whitebox.
Powermock
also have a class named Whitebox
, but unfortunately it looks as if it can not be used with Mockito 2.2.x
Is there any good alternatives in Mockito that I can use to manually "inject" fields, now that Whitebox
is no longer available?
Solution
I wrote in a comment in response to the post made of @JeffBowman. In short I chose to copy the code of WhiteBox, and use that, since it is used in most of the test cases and the class does not have dependencies to other classes. It was the fastest path to solve this issue.
Note The solution that @bcody suggest is a better alternative, if you are using spring, it ads no extra code for you to maintain. I got that information to late :(
解决方案Note that Whitebox
was always in the org.mockito.internal
package. Beyond the incrementing of the major version number, the internal
designation is a giveaway that the package may be subject to breaking changes.
If you do want to make it a point to set otherwise-inaccessible fields in your test, you can do so in the same way that setInternalState
does, which is just to identify the field in the hierarchy, call setAccessible
on it, and then set it. The full code is here on grepcode. You can also examine a number of other ways to set inaccessible state in tests.
public static void setInternalState(Object target, String field, Object value) {
Class<?> c = target.getClass();
try {
Field f = getFieldFromHierarchy(c, field); // Checks superclasses.
f.setAccessible(true);
f.set(target, value);
} catch (Exception e) {
throw new RuntimeException(
"Unable to set internal state on a private field. [...]", e);
}
}
However, in situations like this, my general advice is to stop fighting the tools: Java's four levels of encapsulation (public, protected, package, private) are not necessarily granular enough to express the degree of protection you're trying to express, and it's often much easier to add a well-documented initialization method or constructor override to override the dependencies as you're trying to do reflectively. If you put your tests in the same Java package as the class it tests, you can often even make the fields or method/constructor package-private, which is also a good reason to set up parallel source folders src
and tests
(etc) that represent two halves of the same Java package.
Though some treat this additional method or constructor as "API pollution", I see it instead as coding to the requirements of one of your class's most important consumers—its own test. If you need a pristine external interface, you can easily define one separately such that you can hide any details you'd like. However, you may find you like the ability to inject any real or mock implementation directly into your now-more-flexible component, at which point you may want to look into dependency injection patterns or frameworks.
相关文章