使用JMockit从mocked构造函数返回实际实例

时间:2011-12-05 17:47:27

标签: java junit jmockit

我看了下面的问题,它与我的不一样:

jMockit: How to expect constructor calls to Mocked objects?

这个问题很相似,但答案对我没有帮助:

How to mock the default constructor of the Date class with JMockit?

我要做的是模拟对java.util.zip.ZipFile的构造函数调用,特别是具有java.io.File参数的构造函数调用。我希望构造函数返回一个不同ZipFile的实例,我将使用只接受String参数的构造函数实例化。

这个构造函数调用发生在一个被测试的方法中,所以我不能将我想要的ZipFile作为参数注入。

例如,代码看起来像这样:

public void whatever() {
   //some code
   //some more code
   foo();
   //yet more unrelated code
}

private Blah foo() {
    ZipFile zf;
    //a bunch of code we don't care about

    zf = new ZipFile(someFile);// I want to give it a known zipfile! mock this!


    // some more code we don't care about

    Enumeration<?> entries = zf.entries();
    ZipEntry entry = (ZipEntry) entries.nextElement();
    InputStream is = zf.getInputStream(entry)
    //maybe some other calls to the ZipFile

    // do something else
}

我的第一个想法是使用静态部分模拟执行以下操作:

final ZipFile test = new ZipFile("path/to/actual.zip");
new NonStrictExpectations() {
    @Mocked("(java.io.File)")
    ZipFile zf;
    {
        new ZipFile((File) any); result = test;
    }
};

但是这不会像教程中的这一行所示那样有效:constructors have void return type, so it makes no sense to record return values for them

我的第二个想法是尝试以下方法:

new NonStrictExpectations() {
    {
        newInstance("java.util.zip.ZipFile", new File("path/to/actual.zip"));
    }
};

但是在尝试初始化文件时会抛出以下内容:

java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(Unknown Source)
at java.util.zip.ZipFile.<init>(Unknown Source)

我的第三个想法是使用@MockClass,如下所示:

@Before
public void setUp() throws Exception {
    Mockit.setUpMocks(MockedZipFile.class);
}
@After
public void tearDown() {
    Mockit.tearDownMocks();
}

@MockClass(realClass=ZipFile.class)
public static class MockedZipFile {
    public ZipFile it;
    @Mock
    public void $init(File f) throws ZipException, IOException {
        it = new ZipFile("path/to/actual.zip");//this is what would be called
    }
}

但是这会给我的其他一些模拟软件加载我的测试类的不同部分的配置文件。更不用说我会想要不同的测试用例的不同zip文件。

我想我可以嘲笑ZipFile所做的一切,但这会很快变成巨大的痛苦,因为它被称为很多地方,它的输出需要被嘲笑等等因为使用ZipFile的代码是代码的内部代码而公共方法并不真正关心它,所以重构以试图使其可访问会很尴尬。

我觉得有一种方法可以让JMockit允许这个(在调用构造函数时给出一个对象的特定实例),但我无法弄明白。有没有人有任何想法?

编辑:我尝试了@Rogerio建议的方法,但是我遇到了一个新错误。这是我的设置:

final ZipFile test = new ZipFile("path/to/actual.zip");
new NonStrictExpectations() {
    ZipFile zf;
    {
        zf.entries();
        result = test.entries();
        zf.getInputStream((ZipEntry) any);
        result = new Delegate() {
            InputStream getInputStream(ZipEntry entry) throws IOException {                 
                return test.getInputStream(entry);
            }
        };
    }
};

但我得到以下堆栈跟踪:

java.lang.InternalError
at path.to.test.ExtractDataTest$1.<init>(ExtractDataTest.java:61)
at path.to.test.ExtractDataTest.setUp(ExtractDataTest.java:61)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

其中第61行是new NonStrictExpectations() {行。

我真的想说“不是嘲笑这个对象,而是用相同类型的其他对象替换”。也许我表达的很差。

EDIT2:我想我应该包含版本号: 使用Eclipse 3.6.1 Java 1.6.0_26 JMockit 0.999.10

2 个答案:

答案 0 :(得分:1)

JMockit可以模拟ZipFile类,但它会干扰类加载,因为JVM始终使用JarFile子类(每当它从类路径中的jar文件加载一个类时) 。目前,没有简单的方法来避免这种干扰(有一个计划“修复”这个,但这需要时间)。

但是,这个特定的测试用例无论如何都不太适合模拟工具。相反,我建议设置测试,以便在适当的位置提供具有所需内容的实际zip文件。

(另一个编辑) 我刚刚对JMockit应用了一个更改(对于0.999.12版),它允许以下测试通过,前提是工作目录中有一个test.zip文件,并且它包含一个第一行为“test”的文本文件:

@Test
public void mockZipFile() throws Exception
{
    final ZipFile testZip = new ZipFile("test.zip");

    new NonStrictExpectations() {
        @Capturing @Injectable ZipFile mock;

        {
            mock.entries(); result = testZip.entries();

            mock.getInputStream((ZipEntry) any);
            result = new Delegate() {
                InputStream delegate(ZipEntry e) throws IOException {
                    return testZip.getInputStream(e);
                }
            };
        }
    };

    ZipFile zf = new ZipFile("non-existing");
    ZipEntry firstEntry = zf.entries().nextElement();
    InputStream content = zf.getInputStream(firstEntry);
    String textContent = new BufferedReader(new InputStreamReader(content)).readLine();

    assertEquals("test", textContent);
}

但是,对于这样的情况,我仍然建议使用模拟API not 。相反,请使用真实文件。

答案 1 :(得分:-3)

这可能对您没有帮助,但如果您使用的是Mockito或EasyMock,则可以添加PowerMock,它允许您模拟测试代码中新对象的构造。