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

标签 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 
        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 的调用?

最佳答案

信息非常明显:您不能模拟不可见类和最终类。简短回答:创建一个命名类您的匿名类,然后测试这个类!

答案很长,让我们来探究一下原因!

匿名类是final

您实例化一个FilterFactory 的匿名类,当编译器看到一个匿名类时,它会创建一个finalpackage visible 类。所以匿名类不能通过标准方式模拟,即通过 Mockito。

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

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

Declaring class + $ + <order of declaration starting with 1>

模拟匿名类可能但很脆弱(我是认真的) 所以假设匿名类是第十一个被声明的,它将显示为

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

希望对您有所帮助!

关于java - 在测试匿名类中的方法时,如何使用 Powermockito 模拟新对象的构造?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7523389/

相关文章:

c# - 会为匿名变量调用 dispose 吗?

java - 如果实体已经存在,如何通过属性返回实体?

java - Jenkins CLI 连接被拒绝

java - 具有来自两个表的数据的实体

java - Mockito NoSuchElementException when() findById() get() thenReturn()

java - 如何使用mockito在Junit中获取org.apache.felix.scr.annotations.Reference?

java - 从匿名类访问父类时解决歧义

java - 如果将大量对象传递到我的 SwingWorker.process() 方法怎么办?

java - 如何处理 Mockito 中不匹配的参数?

java - 显示不正确修饰符的匿名内部类