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

Java 在匿名类中测试方法时,如何使用Powermockito模拟新对象的构造?,java,mockito,anonymous-class,powermock,object-construction,Java,Mockito,Anonymous Class,Powermock,Object Construction,我想编写一个JUnit测试来验证下面的代码是否使用BufferedInputStream: public static final FilterFactory BZIP2_FACTORY = new FilterFactory() { public InputStream makeFilter(InputStream in) { // a lot of other code removed for clarity BufferedInpu

我想编写一个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的调用?

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

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

我只是遇到了同样的问题。因此,根据需要,您需要准备类,它将创建邪恶类。在您的例子中,邪恶的类是BufferedInputStream和CBZip2InputStream,它们的创建者是一个匿名类,不能在PrepareForTest注释中定义。所以我不得不像你一样做(嗯,刚刚看到你的评论),我将匿名类移动到命名类

消息很明显:你不能模拟不可见的和最终的类。简短回答:为匿名类创建一个命名类,然后测试这个类

长长的回答,让我们来挖掘原因吧

匿名类是最终类 实例化一个匿名类
FilterFactory
,当编译器看到一个匿名类时,它会创建一个final包可见的类。因此,匿名类不能通过标准平均值(即通过Mockito)进行模拟

模仿匿名类:可能,但如果不是黑客的话很脆弱 好,现在假设您希望能够通过Powermock模拟这个匿名类。当前编译器使用以下方案编译匿名类:

Declaring class + $ + <order of declaration starting with 1>
因此,您可以准备测试匿名类:

@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>
PowerMockito.spy
返回spy,它不会更改
InputHelper.BZIP2\u工厂的值。所以你需要通过反射设置这个区域。您可以使用Powermock提供的
白盒
实用程序

结论 用模拟测试匿名过滤器使用的
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意味着设计有问题在我看来,Powermock非常适合遗留软件,但在设计新代码时会使开发人员盲目;由于他们忽略了OOP的优点,我甚至可以说他们正在设计遗留代码。


希望有帮助

以前的文章,但您不需要创建命名类-请使用通配符,如本文所述


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

注释不是问题所在。如果我将InputHelper.BZIP2_工厂从一个匿名内部类更改为一个命名内部类,那么它可以工作。$1内部类+1:)我考虑过,但不敢尝试。另一方面,对于较短的类,我更喜欢使用匿名类和$1类。仅仅为测试命名一个类是很难看的。是的,我同意,但在这种情况下,匿名类非常简单,不需要如此繁重的测试设置。在Zack的例子中,他似乎在处理内部类中更多的代码。最后,它取决于被测试内容的重要性,如果它被认为是重要的,那么代码本身就应该是显而易见的。多亏了你,我意识到,我错过了一些重要的知识。我使用utils(或helper)类来减少用于创建和测试抽象类的代码,当然,我需要它们。这是个错误,所以现在我在测试源代码中创建了一个简单的扩展类,并对其进行了监视,它的正确答案是。但是,如果您正在对单个类进行powermock,它可能看起来像
@PrepareForTest(fullyQualifiedNames=“com.yourpackage.YourClass*”
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]);
    }
}