First things first. If you have to mock system classes then do you have anything slightly smelly in your design? Can you work the direct link between the two out by say introducing a facade between the real dependency (java.lang.System) and the SUT.
Assuming that you can not or do not want to then you have at least two options: PowerMock and JMockIt.
Lets first make a class to test EnvOMatic. EnvOMatic has one method, clean This loads a value from the Environment and cleans of dirty words.
public class EnvOMatic {
public String clean(String key) {
String value = System.getenv(key);
if (value==null) {
return null;
}
return value.replaceAll("Groovy", "naughty");
}
}
PowerMock
Power Mock has some decent documentation for mocking system classes, but lets show an example of it
here.
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.*;
import static junit.framework.Assert.*;
@RunWith(PowerMockRunner.class)
@PrepareForTest( { EnvOMatic.class })
public class TestPowerMock {
@Test
public void assertThatMockingOfFinalSystemClassesWorks() throws Exception {
mockStatic(System.class);
final String key = "boo";
final String expected = "Bye";
expect(System.getenv(key)).andReturn(expected);
replayAll();
assertEquals(expected, new EnvOMatic().clean(key));
verifyAll();
}
}
So in this we tell JUnit to use the PowerMock runner, then specify the SUT that uses the System class via annotations. Then in the test class we inform PowerMock that we are mocking java.lang.System and finally use it as we would use any other EasyMock mock.
One thing that caught me out was that the JUnit 4.7 provided in the "With Dependencies" download did not work. JUnit 4.4 shipped Eclipse did.
JMockIt
Unlike PowerMock above I could not find any documentation on how to do this. The code below is various examples super glued together until it worked.
import org.junit.*;
import org.junit.runner.RunWith;
@RunWith(JMockit.class)
public class TestJMockIt {
@Test
public void assertThatMockingOfFinalSystemClassesWorks() {
Mockit.setUpMocks(MockSystem.class);
final String key = "boo";
final String expected = "Bye";
MockSystem.put(key, expected);
assertEquals(expected, new EnvOMatic().clean(key));
}
@MockClass(realClass = System.class)
public static class MockSystem {
static Map
static void put(String key, String value) {
env.put(key, value);
}
@Mock
public static String getenv(String key) {
return env.get(key);
}
}
}
One thing to note about this is that we are not mocking but stubbing java.lang.System. Now in this case I don't think this is a problem.
To make this work we need to use a custom runner again, this time JMockit. This runner does requires JUnit 4.5 or above.
Then we create a class with static methods stubs defined.
Finally we inform MockIt of the stub implementation.
So there you have two ways of stubbing and mocking java.lang.System. They both have same restriction - a custom runner, and I'm sure they are equally as powerful, so the choice of which to use will depend solely on your API preference.

0 comments:
Post a Comment