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

问题描述:

我看过以下问题,它与我的问题不同:

I've looked at the following question and it is not the same as mine:

jMockit:如何期望构造函数对模拟对象的调用?

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

This question is similar but the answer is not helpful to me:

如何模拟默认值JMockit构造Date类的构造函数?

我想做的是模拟对java.util.zip.ZipFile的构造函数调用,特别是具有java.io.File参数的构造函数调用.我想让构造函数返回一个不同的ZipFile的实例,我将用仅带有String参数的构造函数实例化一个实例.

What I am trying to do is mock a constructor call to java.util.zip.ZipFile, specifically the one that has a java.io.File argument. I would like for the constructor to return an instance of a different ZipFile, one I will instantiate with the constructor that only takes a String argument.

此构造函数调用在被测方法内部进行,因此无法将我想要的ZipFile作为参数注入.

This constructor call takes place inside a method under test, so I can't inject the ZipFile I want as a parameter.

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

For example, the code looks something like this:

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
}

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

My first thought was to do the following with static partial mocking:

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

But this won't work as indicated by this line in the tutorial: constructors have void return type, so it makes no sense to record return values for them

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

My second thought was to try the following:

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

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

But this throws the following when trying to initialize the file:

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如下:

My third thought was to use a @MockClass as below:

@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文件.

But this hoses some other mocks I have that load a configuration file for a different part of my test class. Not to mention I will want different zip files for different test cases.

我想我可以嘲笑一切 ZipFile会做的事,但这很快就会变成一个巨大的痛苦,因为它被称为很多地方,它的输出将需要被嘲笑,等等,等等.重构以使其易于访问将很尴尬,因为使用ZipFile的代码是该代码的内部代码,而公共方法并不真正在乎它.

I suppose I could mocking everything the ZipFile would do, but this would quickly become a giant pain as it's called lots of places, it's output would be need to be mocked, etc, etc. Refactoring to try to make this accessible would be awkward, as the code that uses the ZipFile is internal to the code and the public methods don't really care about it.

我有一种感觉,JMockit可以允许它(在调用构造函数时提供一个对象的特定实例),但是我无法弄清楚.有人有什么想法吗?

I have a feeling there is a way for JMockit to allow this (giving a particular instance of an object when a constructor is called), but I can't figure it out. Does anyone have any ideas?

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

I tried the method suggested by @Rogerio, but I have a new error. Here's my setup:

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);
            }
        };
    }
};

但是我得到以下堆栈跟踪:

but I get the following stack trace:

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() {行.

我真的很想说:不要嘲笑这个对象,而要替换这个相同类型的其他对象".也许我表达得很差.

I really want to say "instead of mocking this object, substitute this other object of the same type". Maybe I have expressed that poorly.

我认为我应该包括版本号: 使用Eclipse 3.6.1 Java 1.6.0_26 JMockit 0.999.10

I figured I should include version numbers: Using Eclipse 3.6.1 Java 1.6.0_26 JMockit 0.999.10

JMockit可以模拟ZipFile类,但是由于JarFile子类一直被JVM使用(每次加载时,它都会干扰类加载)类路径中jar文件中的类).当前,没有简单的方法来避免这种干扰(有计划解决"此问题,但这需要时间).

JMockit can mock the ZipFile class, but it interferes with class loading since the JarFile subclass is used by the JVM all the time (whenever it loads a class from a jar file in the classpath). Currently, there is no easy way to avoid this interference (there is a plan to "fix" this, but it will take time).

但是,这个特定的测试用例还是不太适合作为模拟工具.相反,我建议您设置测试,以便在适当的位置提供包含所需内容的实际zip文件.

However, this particular test case isn't very suited for a mocking tool anyway. Instead, I would recommend setting up the test so that it provides an actual zip file with the desired contents in the proper place.

(另一个编辑) 我刚刚对JMockit(针对0.999.12版)进行了更改,只要工作目录中有一个test.zip文件,并且包含一个第一行为"test"的文本文件,它就可以通过以下测试: /p>

(another edit) I just applied a change to JMockit (for release 0.999.12) which allows the following test to pass, provided there is a test.zip file in the working dir, and it contains a text file whose first line is "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.而是使用真实文件.

However, I would still recommend not using a mocking API for cases like this. Instead, use a real file.