Question

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

jMockit: How to expect constructor calls to Mocked objects?

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

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

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.

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

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)

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

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.

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.

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?

EDIT: 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)

where line 61 is the new NonStrictExpectations() { line.

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

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

Was it helpful?

Solution

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).

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.

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

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

OTHER TIPS

This probably won't help you, but if you were using Mockito or EasyMock, you could add PowerMock, which allows you to mock the construction of new objects in your code under test.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top