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...
Constructs a process builder with the specified operating system program and arguments. This is a convenience constructor that sets the process builder's command to a string list containing the same strings as the command array, in the same order.
A JUnit test is a method contained in a class which is only used for testing. This is called a Test class. To mark a method as a test method, annotate it with the @Test annotation. This method executes the code under test.
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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With