easymock中提供對于類的mock功能,我們可以方便的mock這個類的某些方法,指定預期的行為以便測試這個類的調用者。這種場景下被mock的類在測試案例中扮演的是次要測試對象或者說依賴的角色,主要測試對象是這個mock類的調用者。但是有時候我們需要將這個測試類作為主要測試對象,我們希望這個類中的部分(通常是大部分)方法保持原有的正常行為,只有個別方法被我們mock掉以便測試。
1. 使用方法
我們先來看看這個partial class mocking 是如何工作的:

public class Service {


public void execute() {
actualMethod();
needMockMethod();
}


void actualMethod() {
System.out.println("call actualMethod()");
}


public void needMockMethod() {
System.out.println("call needMockMethod()");
}

}
我們給出了一個非常簡單的類,我們將要測試execute()方法,期望能測試到actualMethod()這個方法的正常行為,然后需要mock掉needMockMethod().

public class PartialClassMockTest extends Assert {

@Test

public void testPartialMock() {
Service service = EasyMock.createMockBuilder(Service.class).addMockedMethod("needMockMethod").createMock();
service.needMockMethod();
EasyMock.expectLastCall();

EasyMock.replay(service);
service.execute();
EasyMock.verify(service);
}
}
上面的測試案例運行通過,輸出為"call actualMethod()",沒有"call needMockMethod()",說明我們設置的mock生效了。我們創建的mock類的確是只有部分我們制定的方法是mock的,其他都是正常行為。
再來看看為什么我們要需要partial class mocking 這個功能?為什么需要mock掉其中的一個方法?
我們來看看下面這個更加真實的例子:

public class Service {


public String execute2() {
return getConfiguration();
}


public String getConfiguration() {
return Configuration.getUsername();
}
}


public class Configuration {

public static String getUsername() {
//ignore the code to get configuration from file or database
return "username";
}
}
這里例子中,需要測試的 execute2()方法需要調用getConfiguration()方法,而getConfiguration()方法則調用了Configuration的靜態方法來獲取配置信息。我們假設讀取配置的代碼比較復雜不能直接在單元測試環境下運行,因此通過情況下這里的execute2()方法就會因為這個getConfiguration()而造成無法測試。因此我們可以考慮通過partial class mocking的功能來mock掉getConfiguration()方法從而使得我們的測試案例可以覆蓋到execute2()方法
@Test

public void testStaticMethod() {
Service service = EasyMock.createMockBuilder(Service.class).addMockedMethod("getConfiguration").createMock();
EasyMock.expect(service.getConfiguration()).andReturn("abc");

EasyMock.replay(service);
assertEquals("abc", service.execute2());
EasyMock.verify(service);
}
這個測試案例可以正常通過,我們通過partial class mocking成功的避開了getConfiguration()這個絆腳石。
當然這里的實例代碼本身就有點問題,應該采用DI的方法將configuration注入進來,而不是在內部通過靜態方法來獲取。因此一個建議是在使用partial class mocking功能前,先看看是不是可以通過重構來顯改進測試類。只有當我們有足夠充分的不得已的理由時,才使用partial class mocking這種變通(或者說取巧)的方式來解決問題。
2. 限制
上面兩個例子中,我們仔細看看會發現,被mock的方法都是public的。我們試著將方法修改為protected和default,partial class mocking依然生效。但是修改為private之后,則拋出異常:
java.lang.IllegalArgumentException: Method not found (or private): needMockMethod
at org.easymock.internal.MockBuilder.addMockedMethod(MockBuilder.java:75)
at net.sourcesky.study.easymock.tutorial.PartialClassMockTest.testPartialMock(PartialClassMockTest.java:52)
或者將mock的方法繼續保持public,但是加上final,則拋出以下異常:
java.lang.IllegalStateException: no last call on a mock available
at org.easymock.EasyMock.getControlForLastCall(EasyMock.java:521)
at org.easymock.EasyMock.expectLastCall(EasyMock.java:512)
at net.sourcesky.study.easymock.tutorial.PartialClassMockTest.testPartialMock(PartialClassMockTest.java:54)
我們回到之前的章節,class mocking里面講述了class mocking的一些限制:private方法和final方法是不能mock的。partial class mocking下這些限制依然存在。因此,為了開啟partial class mocking,我們不得不稍微破壞一下類的封裝原則,對于原本應該是private的方法,修改為protected或者default。
不得不再次申明,partial class mocking不是一個足夠好的解決方案,它只適合在不得已的情況下使用,不要太依賴這個特性。重構代碼改善代碼才是王道。
3. 疑問
另外class mocking中還講到,對于類的equals(), toString()和hashCode()這三個方法,class mocking下是easymock為這三個方法內建了easymock的實現,因此也不能mock。而partial class mocking,這三個方法同樣不能mock,但是easymock不再為它們內建實現,而是使用它們正常的功能。
關于這點還是有一點疑問,我在easymock的官方文檔中看到以下描述
Remark: EasyMock provides a default behavior for Object's methods (equals, hashCode, toString). However, for a partial mock, if these methods are not mocked explicitly, they will have their normal behavior instead of EasyMock default's one.
言下之意,似乎equals, hashCode, toString這三個方法還是可以顯式mock的。但是我測試了一下:

public class Service {


public String execute3() {
actualMethod();
return toString();
}

@Override

public String toString() {
return "defaultToString()";
}
}

@Test

public void testToStringMethod() {
Service service = EasyMock.createMockBuilder(Service.class).addMockedMethod("toString").createMock();
EasyMock.expect(service.toString()).andReturn("abc");
EasyMock.replay(service);
assertEquals("abc", service.execute3());
EasyMock.verify(service);
}
toString()方法的mock沒能生效,拋出異常:
java.lang.IllegalStateException: no last call on a mock available
at org.easymock.EasyMock.getControlForLastCall(EasyMock.java:521)
at org.easymock.EasyMock.expect(EasyMock.java:499)
at net.sourcesky.study.easymock.tutorial.PartialClassMockTest.testToStringMethod(PartialClassMockTest.java:74)
可以看到明顯是EasyMock.expect(service.toString()).andReturn("abc"); 這里的record沒有成功。