在匿名类中测试方法时,如何使用Powermockito来模拟新对象的构造?

我想写一个JUnit测试来validation下面的代码使用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的调用会引发此消息的exception:

 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的匿名类,当编译器看到一个匿名类时,它会创建一个finalpackage visible类。 所以匿名课程不能通过标准手段来模仿,即通过Mockito。

模拟匿名类:可能但是如果不是HACKY则脆弱

好的,现在假设您希望能够通过Powermock模拟这个匿名类。 当前编译器使用以下方案编译匿名类:

 Declaring class + $ +  

模拟匿名类可能但很脆弱(我的意思是)所以假设匿名类是第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 + $ +  +  

此外,我不认为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的值。 所以你需要通过reflection这个字段来实际设置。 您可以使用Powermock提供的Whitebox实用程序。

结论

用mocks测试匿名filter使用BufferedInputStream太麻烦了。

替代

我宁愿写下面的代码:

一个将使用命名类的输入助手,我不使用接口名称向用户说明此filter的意图是什么!

 public class InputHelper { public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory(); } 

现在filter本身:

 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的优点,我甚至会说他们正在设计遗留代码。

希望有所帮助!

旧post,但你不需要创建一个命名类 – 使用通配符,而不是像这篇文章中提到的powermock模拟构造函数通过whennew()不能与匿名类一起使用

@PrepareForTest(fullyQualifiedNames = "com.yourpackage.containing.anonclass.*")

我刚刚遇到同样的问题。 因此,根据构造函数模拟的文档,您需要准备类,这将创建邪恶的类。 在您的情况下,邪恶类是BufferedInputStream和CBZip2InputStream,它们的创建者是匿名类,无法在PrepareForTest注释中定义。 所以我必须像你一样做(嗯,只是看到你的评论),我将匿名类移动到命名类

您需要使用PowerMockito运行程序运行测试,并且您需要告诉框架哪个类应该具有自定义行为。 在测试类上添加以下类注释:

 @RunWith(PowerMockRunner.class) @PrepareForTest({ BufferedInputStream.class })