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

Zac*_*ack 7 java anonymous-class mockito 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);
    }
};
Run Code Online (Sandbox Code Playgroud)

(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);
}
Run Code Online (Sandbox Code Playgroud)

对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.
Run Code Online (Sandbox Code Playgroud)

我应该使用什么而不是PowerMocktio.spy来设置对whenNew的调用?

Bri*_*ice 12

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

答案很长,让我们挖掘原因吧!

匿名课程是最终的

实例化一个匿名类FilterFactory,当编译器看到一个匿名类时,它会创建一个finalpackage visible类.因此匿名类不能通过标准均值来模拟,即通过Mockito.

嘲笑匿名类:可能但是如果不是HACKY则脆弱

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

Declaring class + $ + <order of declaration starting with 1>
Run Code Online (Sandbox Code Playgroud)

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

InputHelper$11.class
Run Code Online (Sandbox Code Playgroud)

所以你可能准备测试匿名类:

@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
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码将编译,但最终将报告为IDE的错误.IDE可能不知道InputHelper$11.class.IntelliJ谁不使用编译类来检查代码报告.

此外,匿名类命名实际上取决于声明的顺序是一个问题,当有人之前添加另一个匿名类时,编号可能会改变.匿名类是保持匿名的,如果编译器人决定有一天使用字母甚至随机标识符会怎样!

因此,通过Powermock模拟匿名类是可能的但是很脆弱,不要在真正的项目中这样做!

编辑注意: Eclipse编译器具有不同的编号方案,它总是使用3位数字:

Declaring class + $ + <pad with 0> + <order of declaration starting with 1>
Run Code Online (Sandbox Code Playgroud)

另外我认为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);
Run Code Online (Sandbox Code Playgroud)

PowerMockito.spy返回间谍,它不会改变值InputHelper.BZIP2_FACTORY.所以你需要通过反射这个字段来实际设置.您可以使用WhiteboxPowermock提供的实用程序.

结论

用匿名过滤器使用的模拟进行测试太麻烦了BufferedInputStream.

替代

我宁愿写下面的代码:

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

public class InputHelper {
    public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory();
}
Run Code Online (Sandbox Code Playgroud)

现在过滤器本身:

public class BufferedBZIP2FilterFactory {
    public InputStream makeFilter(InputStream in) {
        BufferedInputStream buffer = new BufferedInputStream(in);
        return new CBZip2InputStream(buffer);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以编写如下测试:

@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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

但如果你强迫CBZip2InputStream它只接受,最终可以避免这个测试场景的电源模拟BufferedInputStream.通常使用Powermock意味着设计出了问题.在我看来,Powermock非常适合遗留软件,但在设计新代码时可能会使开发人员失明; 因为他们忽略了OOP的优点,我甚至会说他们正在设计遗留代码.

希望有所帮助!


Mat*_*rne 6

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

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