views:

287

answers:

5

Can anyone make any suggestions about how best to use EasyMock to expect a call to Runtime.getRuntime().exec(xxx)?

I could move the call into a method in another class that implements an interface, but would rather not in an ideal world.

interface RuntimeWrapper {
    ProcessWrapper execute(String command) throws IOException;
}

interface ProcessWrapper {
    int waitFor() throws InterruptedException;
}

I was wondering if anyone had any other suggestions?

+6  A: 

Your class shouldn't call Runtime.getRuntime(). it should expect a Runtime to be set as its dependency, and work with it. Then in your test you can easily provide a mock and set it as a dependency.

As a sidenote, I'd suggest watching this lecture on OO Design for testability.

Update: I didn't see the private constructor. You can try using java bytecode instrumentation in order to add another constructor or make the constructor public, but that might turn out to be impossible as well (if there are some restrictions on that class).

So your option is to make a wrapper (as you suggested in the question), and follow the dependency-injection approach.

Bozho
Thanks for the suggestion - I agree that injecting the dependency is the best way, but I would prefer to mock it. However, I can't see a way to get a mocked instance of Runtime - it's not an interface and I'm not sure I can subclass it because it has a private constructor. Perhaps I'm missing something?
Rich
yup, that makes it next to impossible. Check my update.
Bozho
I'm going to go with the wrapper approach :) Thanks again!
Rich
A: 

Perhaps instead of mocking Runtime.getRuntime().exec() you could "mock" the script/program/etc. it's supposed to be calling.

Instead of passing the real command-line string into exec(), write a test script and execute it instead. You could have the script return hard-coded values you could test against just like a mocked class.

Nate
That's what I tried at first, but I found a few problems with that. First of all it breaks the platform independence of the tests (even though the code is designed for Windows, the tests are often run on a Linux box) and secondly, for some reason it scares me mocking the script. Probably because I'm scared of checking it in :) Also, mocking runtime lets me simulate different scenarios more easily. Thanks anyway!
Rich
+3  A: 

Bozho above is IMO the Correct Solution. But it is not the only solution. You could use PowerMock or JMockIt.

Using PowerMock:

package playtest;

public class UsesRuntime {
    public void run() throws Exception {
        Runtime rt = Runtime.getRuntime();
        rt.exec("notepad");
    }
}


package playtest;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.legacy.PowerMockRunner;

import static org.powermock.api.easymock.PowerMock.*;
import static org.easymock.EasyMock.expect;

@RunWith(PowerMockRunner.class)
@PrepareForTest( { UsesRuntime.class })
public class TestUsesRuntime {

    @Test
    public void test() throws Exception {
        mockStatic(Runtime.class);
        Runtime mockedRuntime = createMock(Runtime.class);

        expect(Runtime.getRuntime()).andReturn(mockedRuntime);

        expect(mockedRuntime.exec("notepad")).andReturn(null);

        replay(Runtime.class, mockedRuntime);

        UsesRuntime sut = new UsesRuntime();
        sut.run();
    }
}
mlk
Thanks for the suggestion, I hadn't heard of Powermock before.
Rich
A: 

Hi, Could you possibly provide the implementation of the ProcessWrapper and RuntimeWrapper please? I'm trying to follow the approach but getting stuck half way. Thanks, S.

Sam
Sure:public class RuntimeWrapperImpl implements RuntimeWrapper { public ProcessWrapper execute(final String executionPath) throws IOException { return new ProcessWrapperImpl(Runtime.getRuntime().exec(executionPath)); }}public final class ProcessWrapperImpl implements ProcessWrapper { private final Process process; public ProcessWrapperImpl(Process process) { this.process = process; } public int exitValue() { return process.exitValue(); } public void waitFor() throws InterruptedException { process.waitFor(); }}
Rich
Horrific formatting - I hope that's clear enough!
Rich
A: 

Here is how you would do it with EasyMock 3.0 (and JUnit 4):

import org.junit.*;
import org.easymock.*;
import static org.easymock.EasyMock.*;

public final class EasyMockTest extends EasyMockSupport
{
    @Test
    public void mockRuntimeExec() throws Exception
    {
         Runtime r = createNiceMock(Runtime.class);

         expect(r.exec("command")).andReturn(null);
         replayAll();

         // In tested code:
         r.exec("command");

         verifyAll();
    }
}

The only problem with the test above is that the Runtime object needs to be passed to code under test, which prevents it from using Runtime.getRuntime(). With JMockit, on the other hand, the following test can be written, avoiding that problem:

import org.junit.*;
import mockit.*;

public final class JMockitTest
{
    @Test
    public void mockRuntimeExec() throws Exception
    {
        final Runtime r = Runtime.getRuntime();

        new NonStrictExpectations(r) {{ r.exec("command"); times = 1; }};

       // In tested code:
       Runtime.getRuntime().exec("command");
    }
}
Rogerio