如何使用EasyMock模拟私有Dialog对象实例化及私有变量
针对你这个场景,要模拟私有Dialog和内部匿名类DialogChangeListener,核心是要突破私有访问限制,同时让EasyMock能接管实例化和回调逻辑。下面一步步给你拆解方案:
先明确待测试类代码
class TestClass { private Dialog dialog; private DialogChangeListener listener = new DialogChangeListener() { public void onChange(Event e) { String v = e.getValue(); if (condition1) { // perform operation 1 } else if (condition2) { // perform operation 2 } else if (condition3) { // perform operation 3 } } }; public void openDialog() { if (condition1) { dialog = new Dialog(arg1, arg2, listener); } else if (condition2) { dialog = new Dialog(arg1, arg2, arg3, listener); } else if (condition3) { dialog = new Dialog(arg1, arg2, arg3, listener); } } }
核心难点分析
这里有两个关键卡点:
Dialog是在openDialog里直接new出来的,没法直接注入Mock对象;listener是私有匿名内部类,既没法直接访问,也不好直接替换成Mock。
解决方案(分两种情况)
情况1:允许修改源代码(推荐,让代码更可测试)
如果能微调待测试类,最好把Dialog的创建逻辑抽成一个可重写的方法,这样不用依赖PowerMock,只用EasyMock就能搞定:
重构后的TestClass
class TestClass { private Dialog dialog; private DialogChangeListener listener = new DialogChangeListener() { public void onChange(Event e) { String v = e.getValue(); if (condition1) { // perform operation 1 } else if (condition2) { // perform operation 2 } else if (condition3) { // perform operation 3 } } }; // 把Dialog创建抽成protected方法,方便测试时重写 protected Dialog createDialog() { if (condition1) { return new Dialog(arg1, arg2, listener); } else if (condition2) { return new Dialog(arg1, arg2, arg3, listener); } else { return new Dialog(arg1, arg2, arg3, listener); } } public void openDialog() { dialog = createDialog(); } }
对应的测试代码
import org.easymock.EasyMock; import org.junit.Test; public class TestClassTest { @Test public void testOpenDialogAndListenerCallback() throws Exception { // 1. 创建TestClass的partial mock,只重写createDialog方法 TestClass testObj = EasyMock.createMockBuilder(TestClass.class) .addMockedMethod("createDialog") .createMock(); // 2. 模拟Dialog对象 Dialog mockDialog = EasyMock.createMock(Dialog.class); // 3. 预期createDialog方法返回mockDialog EasyMock.expect(testObj.createDialog()).andReturn(mockDialog); // 4. 获取私有listener(通过反射) java.lang.reflect.Field listenerField = TestClass.class.getDeclaredField("listener"); listenerField.setAccessible(true); DialogChangeListener actualListener = (DialogChangeListener) listenerField.get(testObj); // 5. 模拟Event,触发condition1的逻辑 Event mockEvent = EasyMock.createMock(Event.class); EasyMock.expect(mockEvent.getValue()).andReturn("trigger-condition1"); // 6. 激活mock,执行测试 EasyMock.replay(testObj, mockDialog, mockEvent); testObj.openDialog(); actualListener.onChange(mockEvent); // 7. 验证所有交互(这里可以根据operation1的逻辑添加对应验证,比如如果operation1调用了某个方法,要提前mock并验证) EasyMock.verify(testObj, mockDialog, mockEvent); } }
情况2:不能修改源代码(用PowerMock+EasyMock配合反射)
如果没法改动原有代码,就得用PowerMock来拦截new Dialog()的构造调用,同时用反射访问私有成员:
测试代码
import org.easymock.EasyMock; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.lang.reflect.Field; @RunWith(PowerMockRunner.class) @PrepareForTest(TestClass.class) // 告诉PowerMock要处理TestClass里的构造方法 public class TestClassTest { @Test public void testOpenDialogWithCondition1() throws Exception { // 1. 创建待测试实例 TestClass testObj = new TestClass(); // 2. 设置私有condition1为true(如果condition1是私有变量的话) Field conditionField = TestClass.class.getDeclaredField("condition1"); conditionField.setAccessible(true); conditionField.set(testObj, true); // 3. 模拟Dialog对象 Dialog mockDialog = EasyMock.createMock(Dialog.class); // 4. 拦截Dialog的构造调用,返回mockDialog PowerMock.expectNew(Dialog.class, arg1, arg2, EasyMock.anyObject(DialogChangeListener.class)) .andReturn(mockDialog); // 5. 获取私有listener Field listenerField = TestClass.class.getDeclaredField("listener"); listenerField.setAccessible(true); DialogChangeListener actualListener = (DialogChangeListener) listenerField.get(testObj); // 6. 模拟Event,触发condition1的操作 Event mockEvent = EasyMock.createMock(Event.class); EasyMock.expect(mockEvent.getValue()).andReturn("condition1-value"); // 7. 激活mock并执行测试 PowerMock.replayAll(); testObj.openDialog(); actualListener.onChange(mockEvent); // 8. 验证所有交互 PowerMock.verifyAll(); } // 如果需要替换listener为Mock对象(比如要验证listener是否被Dialog调用) @Test public void testListenerIsPassedToDialog() throws Exception { TestClass testObj = new TestClass(); // 模拟Dialog和Mock listener Dialog mockDialog = EasyMock.createMock(Dialog.class); DialogChangeListener mockListener = EasyMock.createMock(DialogChangeListener.class); // 替换私有listener成员 Field listenerField = TestClass.class.getDeclaredField("listener"); listenerField.setAccessible(true); listenerField.set(testObj, mockListener); // 设置condition2为true Field conditionField = TestClass.class.getDeclaredField("condition2"); conditionField.setAccessible(true); conditionField.set(testObj, true); // 拦截构造方法,传入mockListener PowerMock.expectNew(Dialog.class, arg1, arg2, arg3, mockListener).andReturn(mockDialog); // 激活并执行 PowerMock.replayAll(); testObj.openDialog(); // 验证构造方法确实传入了mockListener PowerMock.verifyAll(); } }
关键注意事项
DialogChangeListener必须是接口(或者抽象类),否则EasyMock没法创建它的Mock对象;- 如果
arg1/arg2等是私有变量,同样需要用反射设置它们的值; - PowerMock需要配合特定的JUnit Runner和
@PrepareForTest注解,才能拦截构造方法。
内容的提问来源于stack exchange,提问作者user3095047
相关产品推荐
相关产品推荐

