我想编写一个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是一个接口(interface)。)
到目前为止,我的测试看起来像这样:
@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的调用?
最佳答案
信息很明显:您不能模拟不可见的类和最终类。简短答案:创建一个匿名类的命名类,然后测试该类,而不是!
长答案,让我们找出原因吧!
匿名类是最终的
您实例化了一个匿名类FilterFactory
,当编译器看到一个匿名类时,它会创建一个最终和包类。因此,匿名类无法通过标准方式(即Mockito)进行 mock 。
模拟匿名类(class):可能,但如果不是HACKY,则为BRITTLE
好的,现在假设您希望能够通过Powermock模拟该匿名类。当前的编译器使用以下方案来编译匿名类:
Declaring class + $ + <order of declaration starting with 1>
可以模拟匿名类(class),但很脆弱(我是说真的)
因此,假设匿名类是要声明的第十一个类,它将显示为
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明确规定了编译器应如何命名匿名类。
您无需将 spy 重新分配给静态字段
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
返回 spy ,它不会更改InputHelper.BZIP2_FACTORY
的值。因此,您将需要通过反射来实际设置此字段。您可以使用Powermock提供的Whitebox
实用程序。结论
仅使用匿名过滤器使用
BufferedInputStream
的模拟进行测试就太麻烦了。选择
我宁愿编写以下代码:
使用命名类的输入助手,我不会使用接口(interface)名称来向用户表明此过滤器的目的!
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意味着设计出了点问题。 在我看来,Powermock非常适合旧版软件,但是在设计新代码时可能会使开发人员失明。因为他们错过了OOP的优势,所以我什至会说他们在设计遗留代码。 希望能有所帮助!