在匿名类中测试方法时,如何使用 Powermockito 模拟新对象的构造?
我想编写一个 JUnit 测试来验证下面的代码是否使用 BufferedInputStream:
public static final FilterFactory BZIP2_FACTORY = new FilterFactory() {
public InputStream makeFilter(InputStream in) {
// a lot of other code removed for clarity
BufferedInputStream buffer = new BufferedInputStream(in);
return new CBZip2InputStream(buffer);
}
};
(FilterFactory 是一个接口。)
到目前为止,我的测试如下所示:
@Test
public void testBZIP2_FactoryUsesBufferedInputStream() throws Throwable {
InputStream in = mock(InputStream.class);
BufferedInputStream buffer = mock(BufferedInputStream.class);
CBZip2InputStream expected = mock(CBZip2InputStream.class);
PowerMockito.spy(InputHelper.BZIP2_FACTORY); // This line fails
whenNew(BufferedInputStream.class).withArguments(in).thenReturn(buffer);
whenNew(CBZip2InputStream.class).withArguments(buffer).thenReturn(expected);
InputStream observed = InputHelper.BZIP2_FACTORY.makeFilter(in);
assertEquals(expected, observed);
}
对 PowerMockito.spy 的调用引发了一个异常,并显示以下消息:
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class edu.gvsu.cis.kurmasz.io.InputHelper$1
Mockito can only mock visible & non-final classes.
我应该做什么使用而不是PowerMocktio.spy 来设置对whenNew 的调用?
I woud like to write a JUnit test to verify that the code below uses a BufferedInputStream:
public static final FilterFactory BZIP2_FACTORY = new FilterFactory() {
public InputStream makeFilter(InputStream in) {
// a lot of other code removed for clarity
BufferedInputStream buffer = new BufferedInputStream(in);
return new CBZip2InputStream(buffer);
}
};
(FilterFactory is an interface.)
My test thus far looks like this:
@Test
public void testBZIP2_FactoryUsesBufferedInputStream() throws Throwable {
InputStream in = mock(InputStream.class);
BufferedInputStream buffer = mock(BufferedInputStream.class);
CBZip2InputStream expected = mock(CBZip2InputStream.class);
PowerMockito.spy(InputHelper.BZIP2_FACTORY); // This line fails
whenNew(BufferedInputStream.class).withArguments(in).thenReturn(buffer);
whenNew(CBZip2InputStream.class).withArguments(buffer).thenReturn(expected);
InputStream observed = InputHelper.BZIP2_FACTORY.makeFilter(in);
assertEquals(expected, observed);
}
The call to PowerMockito.spy raises an exception with this message:
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class edu.gvsu.cis.kurmasz.io.InputHelper$1
Mockito can only mock visible & non-final classes.
What should I be using instead of PowerMocktio.spy to set up the calls to whenNew?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这个消息非常明显:你不能模拟不可见的和最终的类。简短的回答:创建一个命名类您的匿名类,然后测试这个类!
答案很长,让我们来探究一下原因!
匿名类是final的
你实例化一个
FilterFactory
的匿名类,当编译器看到一个匿名类时,它会创建一个final和包可见类。因此,匿名类不能通过标准方式(即通过 Mockito)进行模拟。模拟匿名类:可能,但如果不是 HACKY,则很脆弱
好吧,现在假设您希望能够通过 Powermock 模拟这个匿名类。当前的编译器使用以下方案编译匿名类:
模拟匿名类可能但很脆弱(我是认真的)
因此,假设匿名类是第十一个要声明的类,它将显示为
因此,您可能会准备测试匿名类:
此代码将编译,但最终将在您的 IDE 中报告为错误。 IDE 可能不知道
InputHelper$11.class
。 IntelliJ 不使用编译类来检查代码报告。另外,匿名类命名实际上取决于声明的顺序也是一个问题,当有人之前添加另一个匿名类时,编号可能会改变。
匿名类是为了保持匿名而设计的,如果编译器有一天决定使用字母甚至随机标识符怎么办!
因此,通过 Powermock 模拟匿名类是可能的,但很脆弱,永远不要在实际项目中这样做!
编辑注意: Eclipse 编译器有不同的编号方案,它总是使用 3 位数字:
另外,我认为 JLS 没有明确指定编译器应如何命名匿名类。
您无需将间谍重新分配给静态字段
PowerMockito.spy
返回间谍,它不会更改InputHelper.BZIP2_FACTORY
的值。因此,您需要通过反射实际设置该字段。您可以使用Powermock提供的Whitebox
实用程序。结论
仅使用模拟来测试匿名过滤器使用 BufferedInputStream 太麻烦了。
我宁愿
编写以下代码:
一个将使用命名类的输入帮助器,我不使用接口名称来向用户明确此过滤器的意图是什么!
现在是过滤器本身:
现在您可以编写这样的测试:
但如果您强制 CBZip2InputStream 只接受 BufferedInputStream ,则最终可以避免此测试场景的 powermock 内容。通常使用Powermock意味着设计有问题。 在我看来,Powermock 非常适合遗留软件,但在设计新代码时可能会蒙蔽开发人员;因为他们忽略了 OOP 的优点,我什至会说他们正在设计遗留代码。
希望这会有所帮助!
The message is pretty obvious: You can't mock non-visible and final classes. Short answer : Create a named class of your anonymous one, and test this class instead!
Long answer, let's dig why !
An anonymous class is final
You instantiate an anonymous class of
FilterFactory
, when the compiler sees an anonymous class, it creates a final and package visible class. So the anonymous class is not mockable through standard mean i.e. through Mockito.Mocking anonymous class : possible but BRITTLE if not HACKY
OK, now suppose you want to be able to mock this anonymous class through Powermock. Current compilers compile anonymous class with following scheme :
Mocking anonymous class possible but brittle (And I mean it)
So supposing the anonymous class is the eleventh to be declared, it will appear as
So you could potentially prepare for test the anonymous class:
This code will compile, BUT will eventually be reported as an error with your IDE. The IDE probably doesn't know about
InputHelper$11.class
. IntelliJ who doesn't use compiled class to check the code report so.Also the fact that the anonymous class naming actually depends on the order of the declaration is a problem, when someone adds another anonymous class before, the numbering could change.
Anonymous classes are made to stay anonymous, what if the compiler guys decide one day to use letters or even random identifiers!
So mocking anonymous classes through Powermock is possible but brittle, don't ever do that in a real project!
EDITED NOTE : The Eclipse compiler has a different numbering scheme, it always uses a 3 digit number :
Also I don't think the JLS clearly specify how the compilers should name anonymous classes.
You don't reassign the spy to the static field
PowerMockito.spy
returns the spy, it doesn't change the value ofInputHelper.BZIP2_FACTORY
. So you would need to actually set via reflection this field. You can use theWhitebox
utility that Powermock provide.Conclusion
Too much trouble to just test with mocks that the anonymous filter uses a
BufferedInputStream
.Alternative
I would rather write the following code:
An input helper that will use the named class, I don't use the interface name to make clear to the user what is the intent of this filter!
And now the filter itself :
Now you can write a test like this :
But could eventually avoid powermock stuff for this test scenario if you force the
CBZip2InputStream
to only acceptBufferedInputStream
. Usually using Powermock means something is wrong with the design. In my opinion Powermock is great for legacy softwares, but can blind developers when designing new code; as they are missing the point of OOP's good part, I would even say they are designing legacy code.Hope that helps !
旧帖子,但您不需要创建命名类 - 请使用通配符,如本文中所述 通过whennew()进行的powermock模拟构造函数不适用于匿名类
@PrepareForTest(fullyQualifiedNames =“com.yourpackage.containing.anonclass.*”)
Old post, but you don't need to create a named class - use wildcards instead as mentioned in this post powermock mocking constructor via whennew() does not work with anonymous class
@PrepareForTest(fullyQualifiedNames = "com.yourpackage.containing.anonclass.*")
您需要使用 PowerMockito 运行程序运行测试,并且需要告诉框架哪个类应该具有自定义行为。在您的测试类上添加以下类注释:
You need to run the test using the PowerMockito runner, and you need to tell the framework which class(es) should have custom behaviour. Add the following class annotations on your test class:
我刚刚遇到了同样的问题。因此,根据构造函数模拟文档,您需要准备该类,这将创建邪恶的阶级。在你的例子中,邪恶的类是 BufferedInputStream 和 CBZip2InputStream,它们的创建者是一个匿名类,不能在PrepareForTest注释中定义。所以我必须像你一样(嗯,刚刚看到你的评论),我将匿名类移动到命名类。
I just came around the same problem. So according to the documentation of constructor mocking you need to prepare the class, which will create the evil class(es). In your case the evil classes are BufferedInputStream and CBZip2InputStream, and the creator of them is an anonymous class, which cannot be defined in PrepareForTest annotation. So I had to do the same as you did (hmm, just saw your comment), I moved the anonymous class to named class.