Question

I have a Java method which starts up a Process with ProcessBuilder, and pipes its output into a byte array, and then returns its byte array when the process is finished.

Pseudo-code:

ProcessBuilder b = new ProcessBuilder("my.exe")
Process p = b.start();
... // get output from process, close process

What would be the best way to go about unit testing this method? I haven't found a way to mock ProcessBuilder (it's final), even with the incredibly awesome JMockit, it gives me a NoClassDefFoundError:

java.lang.NoClassDefFoundError: test/MockProcessBuilder
    at java.lang.ProcessBuilder.<init>(ProcessBuilder.java)
    at mypackage.MyProcess.start(ReportReaderWrapperImpl.java:97)
    at test.MyProcessTest.testStart(ReportReaderWrapperImplTest.java:28)

Any thoughts?


Answer - As Olaf recommended, I ended up refactoring those lines to an interface

Process start(String param) throws IOException;

I now pass an instance of this interface into the class I wanted to test (in its constructor), normally using a default implementation with the original lines. When I want to test I simply use a mock implementation of the interface. Works like a charm, though I do wonder if I'm over-interfacing here...

Was it helpful?

Solution

Shield yourself from the classes to be mocked. Create an interface either for doing what you really want (e.g. hiding the fact that external processes are involved at all) or only for Process and ProcessBuilder.

You don't want to test, that ProcessBuilder and Process work, only that you can work with their output. When you create an interface one trivial implementation (that can be inspected easily) delegates to ProcessBuilder and Process, another implementation mocks this behaviour. Later on you might even have another implementation that does what you need without starting another process.

OTHER TIPS

With newer releases of JMockit (0.98+) you should be able to easily mock JRE classes like Process and ProcessBuilder. So, no need to create interfaces just for testing...

Full example (using JMockit 1.16):

public class MyProcessTest
{
    public static class MyProcess {
        public byte[] run() throws IOException, InterruptedException {
            Process process = new ProcessBuilder("my.exe").start();
            process.waitFor();

            // Simplified example solution:
            InputStream processOutput = process.getInputStream();
            byte[] output = new byte[8192];
            int bytesRead = processOutput.read(output);

            return Arrays.copyOf(output, bytesRead);
        }
   }

    @Test
    public void runProcessReadingItsOutput(@Mocked final ProcessBuilder pb)
        throws Exception
    {
        byte[] expectedOutput = "mocked output".getBytes();
        final InputStream output = new ByteArrayInputStream(expectedOutput);
        new Expectations() {{ pb.start().getInputStream(); result = output; }};

        byte[] processOutput = new MyProcess().run();

        assertArrayEquals(expectedOutput, processOutput);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top