Mocking with JMockit
When writing Unit Tests, it's often necessary to mock classes. There are several good frameworks to do this, such as EasyMock. This works fine in most cases, but it's possible to run into problems when using certain designs.
EasyMock (the same is true for most other mocking frameworks) can only mock public, non static or final methods. In most cases this is not a problem, it will fit most designs. It can be a problem however if you have code that uses, for example, a lot of static methods. It might be an option to refactor this (a lot of static methods might be an indication of bad design), but what if you use classes from external libraries?
JMockit is a small framework that can help out in such cases. It allows you to dynamically replace methods with new definitions. This is based on the Java 1.5 Instrumentation framework. The cool thing about JMockit is that it works with almost every design. It allows you to redefine private, static and final methods. Even no-arg constructors can be redefined.
At the project i'm currently working on we had some trouble figuring out what can and what can't be redefined, and how the method definitions should look like. I've written a small class and a JUnit testcase that redefines all possible methods from the class under test.
Class to mock
public class ClassToMock {
private String memberToSet;
private static String staticMember;
static {
staticMember = "Static initialized";
}
public ClassToMock() {
this.memberToSet = "Member set by original constructor";
}
public ClassToMock(String value) {
this.memberToSet = "Member set by original constructor";
}
public String publicMethod() {
return "Original public method";
}
protected String protectedMethod() {
return "Original protected method";
}
String defaultMethod() {
return "Original default method";
}
public String methodThatUsesPrivateMethod() {
return privateMethod();
}
private String privateMethod() {
return "Original private method";
}
public String getMemberToSet() {
return memberToSet;
}
public String getStaticMember() {
return staticMember;
}
}
Testcase
import junit.framework.TestCase;
import mockit.Mockit;
public class ClassToMockTest extends TestCase {
private ClassToMock mockedClass;
public static class Replacement {
static {
}
public Replacement() {
}
public Replacement(String test) {
}
public String publicMethod() {
return "Replaced public method";
}
public String protectedMethod() {
return "Replaced protected method";
}
public String defaultMethod() {
return "Replaced default method";
}
public String privateMethod() {
return "Replaced private method";
}
}
protected void setUp() throws Exception {
Mockit.redefineMethods(ClassToMock.class, Replacement.class);
mockedClass = new ClassToMock("test");
}
protected void tearDown() throws Exception {
Mockit.restoreAllOriginalDefinitions();
}
/**
* Public methods can be replaced
*/
public void testReplacePublic() {
assertEquals("Replaced public method", mockedClass.publicMethod());
}
/**
* Protected methods can be replaced.
* The replacement method should be declared public however
*/
public void testReplaceProtected() {
assertEquals("Replaced protected method", mockedClass.protectedMethod());
}
/**
* Package accessable methods can be replaced
* The replacement method should be declared public however
*/
public void testReplaceDefault() {
assertEquals("Replaced default method", mockedClass.defaultMethod());
}
/**
* Private methods can be replaced
* The replacement method should be declared public however
*/
public void testReplacePrivate() {
assertEquals("Replaced private method", mockedClass.methodThatUsesPrivateMethod());
}
/**
* Non-default constructors can be replaced
* Your mock definition must have a default constructor however
*/
public void testReplaceConstructor() {
assertEquals(null, mockedClass.getMemberToSet());
}
/**
* Default constructors <b>can't</b> be replaced
*/
public void testReplaceDefaultConstructor() {
mockedClass = new ClassToMock();
assertEquals("Member set by original constructor", mockedClass.getMemberToSet());
}
/**
* Static initializers <b>can't</b> be replaced
*/
public void testReplaceStaticBlock() {
assertEquals("Static initialized", mockedClass.getStaticMember());
}
}