首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在测试匿名类中的方法时,如何使用Powermockito模拟新对象的构造?

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

Stack Overflow用户
提问于 2011-09-23 09:39:31
回答 4查看 47K关注 0票数 12

我想写一个JUnit测试来验证下面的代码是否使用了BufferedInputStream:

代码语言:javascript
复制
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是一个接口。)

到目前为止,我的测试如下所示:

代码语言:javascript
复制
@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的调用引发了一个异常,并显示以下消息:

代码语言:javascript
复制
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.

在设置对whenNew的调用时,我应该使用什么来代替PowerMocktio.spy?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-02-27 23:24:53

这个信息很明显:你不能模拟不可见的和最终的类。简而言之:创建一个匿名类的命名类,然后测试这个类,而不是

答案很长,让我们来挖掘一下原因!

匿名类是最终类。

你实例化了一个匿名的FilterFactory类,当编译器发现一个匿名类时,它会创建一个finalpackage visible类。所以匿名类不能通过标准的方式,也就是通过Mockito来模仿。

模仿匿名类:可能,但很脆弱,如果不是很麻烦的话

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

代码语言:javascript
复制
Declaring class + $ + <order of declaration starting with 1>

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

代码语言:javascript
复制
InputHelper$11.class

因此,您可以为测试匿名类做好准备:

代码语言:javascript
复制
@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中报告为错误。集成开发环境可能不了解InputHelper$11.class。IntelliJ谁不使用编译的类来检查代码报告。

事实上,匿名类的命名实际上取决于声明的顺序,这也是一个问题,当有人之前添加了另一个匿名类时,编号可能会改变。匿名类是为了保持匿名而设计的,如果有一天编译器人员决定使用字母甚至随机标识符怎么办!

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

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

代码语言:javascript
复制
Declaring class + $ + <pad with 0> + <order of declaration starting with 1>

另外,我认为JLS没有明确规定编译器应该如何命名匿名类。

您不会将间谍重新分配到静态字段

代码语言:javascript
复制
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的值。所以你需要通过反射来设置这个字段。您可以使用Powermock提供的Whitebox实用程序。

结论

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

替代方案

我宁愿写下面的代码:

一个将使用命名类的输入帮助器,我不会使用接口名称来向用户说明这个过滤器的目的!

代码语言:javascript
复制
public class InputHelper {
    public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory();
}

现在是过滤器本身:

代码语言:javascript
复制
public class BufferedBZIP2FilterFactory {
    public InputStream makeFilter(InputStream in) {
        BufferedInputStream buffer = new BufferedInputStream(in);
        return new CBZip2InputStream(buffer);
    }
}

现在您可以像这样编写一个测试:

代码语言:javascript
复制
@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意味着设计有问题。在我看来,对于遗留软件来说很棒,但在设计新代码时会使开发人员盲目;因为他们没有注意到OOP的优点,所以我甚至可以说他们正在设计遗留代码。

希望这能有所帮助!

票数 14
EN

Stack Overflow用户

发布于 2014-05-09 08:58:46

旧帖子,但您不需要创建命名类-使用通配符代替本文中提到的powermock mocking constructor via whennew() does not work with anonymous class

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

票数 9
EN

Stack Overflow用户

发布于 2011-10-31 23:47:55

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

代码语言:javascript
复制
@RunWith(PowerMockRunner.class)
@PrepareForTest({ BufferedInputStream.class })
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7523389

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档