我想编写一个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的调用?
答案 0 :(得分:12)
这个消息很明显:你不能模拟不可见的和最终的类。简短回答:创建一个匿名的命名类,然后测试此类!
答案很长,让我们挖掘原因!
实例化FilterFactory
的匿名类,当编译器看到匿名类时,它会创建最终和包可见类。因此,匿名类不能通过标准均值来模拟,即通过Mockito。
好的,现在假设您希望能够通过Powermock模拟这个匿名类。当前编译器使用以下方案编译匿名类:
Declaring class + $ + <order of declaration starting with 1>
嘲弄匿名类可能但很脆弱(我的意思是) 因此,假设匿名类是要声明的第11个,它将显示为
InputHelper$11.class
所以你可能准备测试匿名类:
@RunWith(PowerMockRunner.class)
@PrepareForTest({InputHelper$11.class})
public class InputHelperTest {
@Test
public void anonymous_class_mocking works() throws Throwable {
PowerMockito.spy(InputHelper.BZIP2_FACTORY); // This line fails
}
}
此代码将编译,但最终将报告为IDE的错误。 IDE可能不了解InputHelper$11.class
。 IntelliJ谁不使用编译类来检查代码报告。
此外,匿名类命名实际上取决于声明的顺序是一个问题,当有人之前添加另一个匿名类时,编号可能会改变。 匿名类是保持匿名的,如果编译器人决定有一天使用字母甚至是随机标识符会怎么样呢?
所以通过Powermock模拟匿名类是可能的但很脆弱,不要在真正的项目中这样做!
已编辑注意: Eclipse编译器具有不同的编号方案,它始终使用3位数字:
Declaring class + $ + <pad with 0> + <order of declaration starting with 1>
此外,我认为JLS没有明确说明编译器应如何命名匿名类。
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);
PowerMockito.spy
会返回间谍,但不会更改InputHelper.BZIP2_FACTORY
的值。所以你需要通过反射这个字段来实际设置。您可以使用Powermock提供的Whitebox
实用程序。
使用模拟器测试匿名过滤器使用BufferedInputStream
太麻烦了。
我宁愿写下面的代码:
将使用指定类的输入助手,我不使用接口名称向用户说明此过滤器的意图是什么!
public class InputHelper {
public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory();
}
现在过滤器本身:
public class BufferedBZIP2FilterFactory {
public InputStream makeFilter(InputStream in) {
BufferedInputStream buffer = new BufferedInputStream(in);
return new CBZip2InputStream(buffer);
}
}
现在你可以写一个这样的测试:
@RunWith(PowerMockRunner.class)
public class BufferedBZIP2FilterFactoryTest {
@Test
@PrepareForTest({BufferedBZIP2FilterFactory.class})
public void wraps_InputStream_in_BufferedInputStream() throws Exception {
whenNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class))
.thenReturn(Mockito.mock(CBZip2InputStream.class));
new BufferedBZIP2FilterFactory().makeFilter(anInputStream());
verifyNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class));
}
private ByteArrayInputStream anInputStream() {
return new ByteArrayInputStream(new byte[10]);
}
}
但如果强迫CBZip2InputStream
仅接受BufferedInputStream
,最终可能会避免此测试场景的电源模式。通常使用Powermock意味着设计出了问题。 在我看来,Powermock非常适合遗留软件,但在设计新代码时可能会使开发人员失明;因为他们忽略了OOP的优点,我甚至会说他们正在设计遗留代码。
希望有所帮助!
答案 1 :(得分:6)
旧帖子,但您不需要创建命名类 - 请使用通配符,如本文powermock mocking constructor via whennew() does not work with anonymous class中所述
@PrepareForTest(fullyQualifiedNames = "com.yourpackage.containing.anonclass.*")
答案 2 :(得分:0)
您需要使用PowerMockito运行程序运行测试,并且需要告诉框架哪些类应该具有自定义行为。在测试类上添加以下类注释:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ BufferedInputStream.class })
答案 3 :(得分:0)
我刚刚遇到同样的问题。所以根据documentation of constructor mocking你需要准备这个类,这将创建邪恶的类。在你的情况下,邪恶的类是BufferedInputStream和CBZip2InputStream,它们的创建者是一个匿名类,不能在PrepareForTest注释中定义。所以我必须像你一样做(嗯,只看到你的评论),我将匿名类移动到命名类。