如何测试内部通过new创建对象的方法?Mock FooWrapper实现测试
问题描述
需要测试getFoolList方法,该方法内部通过new直接创建FooWrapper实例。希望Mock FooWrapper,让wrapper.connect()不执行实际操作,调用wrapper.retrieveFileBytes(path.replace("somePath", ""))时返回指定byte[],询问是否有可行的实现方式。
原方法代码
@Override public List<Foo> getFoolList(String path) { try (FooWrapper wrapper = new FooWrapper( host, port, login, password, logging, connectionTimeOut) ) { wrapper.connect(); byte[] archiveBytes = wrapper.retrieveFileBytes(path.replace("somePath", "")); // some logic to process archiveBytes and return List<Foo> } catch (Exception e) { // exception handling } }
预期测试代码示例
FooWrapper wrapper = Mockito.mock(FooWrapper.class); when(wrapper.connect()).thenDoNothing(); when(wrapper.retrieveFileBytes(any())).thenReturn(someBytes); // 调用getFoolList并断言 assertTrue(getFooList("test").get(0) instanceof Foo);
可行实现方案
直接在方法内new对象会导致无法直接用Mockito mock,因为Mockito只能处理被注入或通过工厂获取的实例。以下是几种可行的改造+测试方案:
方案1:重构代码,依赖注入FooWrapper(推荐)
最合理的方式是抽离FooWrapper的创建逻辑,通过依赖注入的方式传入,测试时直接注入mock实例。
重构后的原代码
public class FooService { private final FooWrapperFactory wrapperFactory; // 其他字段:host, port, login等 // 构造器注入工厂 public FooService(FooWrapperFactory wrapperFactory, String host, int port, String login, String password, boolean logging, int connectionTimeOut) { this.wrapperFactory = wrapperFactory; // 初始化其他字段 } @Override public List<Foo> getFoolList(String path) { try (FooWrapper wrapper = wrapperFactory.create(host, port, login, password, logging, connectionTimeOut)) { wrapper.connect(); byte[] archiveBytes = wrapper.retrieveFileBytes(path.replace("somePath", "")); // some logic } } } // 定义工厂接口 public interface FooWrapperFactory { FooWrapper create(String host, int port, String login, String password, boolean logging, int connectionTimeOut); } // 生产环境的工厂实现 public class DefaultFooWrapperFactory implements FooWrapperFactory { @Override public FooWrapper create(String host, int port, String login, String password, boolean logging, int connectionTimeOut) { return new FooWrapper(host, port, login, password, logging, connectionTimeOut); } }
测试代码
// mock工厂和Wrapper实例 FooWrapperFactory mockFactory = Mockito.mock(FooWrapperFactory.class); FooWrapper mockWrapper = Mockito.mock(FooWrapper.class); when(mockFactory.create(anyString(), anyInt(), anyString(), anyString(), anyBoolean(), anyInt())).thenReturn(mockWrapper); // 配置mock行为 when(mockWrapper.connect()).thenDoNothing(); byte[] testBytes = new byte[]{1,2,3}; when(mockWrapper.retrieveFileBytes(anyString())).thenReturn(testBytes); // 创建被测服务并注入mock工厂 FooService service = new FooService(mockFactory, "testHost", 1234, "testLogin", "testPass", true, 1000); // 调用方法并断言 List<Foo> result = service.getFoolList("somePath/test"); assertTrue(result.get(0) instanceof Foo);
方案2:使用PowerMock mock new操作(临时方案)
如果无法重构原代码,可以用PowerMock拦截new操作返回mock实例,但该方案会修改字节码,增加测试复杂度,长期维护性差。
测试代码(JUnit4 + PowerMock)
@RunWith(PowerMockRunner.class) @PrepareForTest(FooService.class) // 指定需要mock new操作的类 public class FooServiceTest { @Test public void testGetFoolList() throws Exception { // 创建mock Wrapper并配置行为 FooWrapper mockWrapper = Mockito.mock(FooWrapper.class); when(mockWrapper.connect()).thenDoNothing(); byte[] testBytes = new byte[]{1,2,3}; when(mockWrapper.retrieveFileBytes(anyString())).thenReturn(testBytes); // 拦截new FooWrapper(...)调用,返回mock实例 PowerMockito.whenNew(FooWrapper.class) .withArguments(anyString(), anyInt(), anyString(), anyString(), anyBoolean(), anyInt()) .thenReturn(mockWrapper); // 创建被测服务并测试 FooService service = new FooService("testHost", 1234, "testLogin", "testPass", true, 1000); List<Foo> result = service.getFoolList("somePath/test"); assertTrue(result.get(0) instanceof Foo); } }
方案3:抽离创建逻辑为私有方法(折衷方案)
若不想引入工厂类,可把FooWrapper的创建逻辑抽成私有方法,再用Mockito的spy mock这个方法。
重构后的原代码
public class FooService { // 其他字段:host, port等 @Override public List<Foo> getFoolList(String path) { try (FooWrapper wrapper = createFooWrapper()) { wrapper.connect(); byte[] archiveBytes = wrapper.retrieveFileBytes(path.replace("somePath", "")); // some logic } } // 抽离创建逻辑为私有方法 private FooWrapper createFooWrapper() { return new FooWrapper(host, port, login, password, logging, connectionTimeOut); } }
测试代码
@Test public void testGetFoolList() throws Exception { // spy被测对象,保留原有逻辑但可mock私有方法 FooService service = Mockito.spy(new FooService("testHost", 1234, "testLogin", "testPass", true, 1000)); FooWrapper mockWrapper = Mockito.mock(FooWrapper.class); // mock私有方法返回mock实例 PowerMockito.doReturn(mockWrapper).when(service, "createFooWrapper"); // 配置mock行为 when(mockWrapper.connect()).thenDoNothing(); byte[] testBytes = new byte[]{1,2,3}; when(mockWrapper.retrieveFileBytes(anyString())).thenReturn(testBytes); // 调用并断言 List<Foo> result = service.getFoolList("somePath/test"); assertTrue(result.get(0) instanceof Foo); }
内容的提问来源于stack exchange,提问作者user19282140
相关产品推荐
相关产品推荐

