如何Mock Java中不返回实例的单例类?
解决方案
方案一:使用PowerMock扩展Mockito实现Mock(无需修改原代码)
PowerMock是Mockito的扩展,支持Mock静态方法、私有方法以及final类,正好适配你的场景:
1. 添加依赖(Maven示例)
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
2. 编写测试类
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest({Connection.class}) // 标记需要修改字节码的类 public class ManagerClassTest { @Test public void testPrepareForThing() throws Exception { // 1. Mock Connection类的静态方法 PowerMockito.mockStatic(Connection.class); // 2. 拦截Connection实例的创建,并Mock其私有方法privateMethodCall Connection mockConn = PowerMockito.mock(Connection.class); PowerMockito.whenNew(Connection.class).withNoArguments().thenReturn(mockConn); PowerMockito.doNothing().when(mockConn, "privateMethodCall"); // 模拟私有方法的行为,比如什么都不做 // 3. 执行测试方法 ManagerClass manager = new ManagerClass(); manager.prepareForThing(); // 4. 验证行为(可选) PowerMockito.verifyStatic(Connection.class); Connection.get(); // 验证静态方法被调用 PowerMockito.verify(mockConn).invoke("privateMethodCall"); // 验证私有方法被调用 } }
复用Mock逻辑
如果要让Mock对象复用,可以把Mock的逻辑抽成公共方法或者父类:
public class BaseConnectionMockTest { protected void mockConnection() throws Exception { PowerMockito.mockStatic(Connection.class); Connection mockConn = PowerMockito.mock(Connection.class); PowerMockito.whenNew(Connection.class).withNoArguments().thenReturn(mockConn); PowerMockito.doNothing().when(mockConn, "privateMethodCall"); } }
其他测试类继承这个父类,在测试方法中调用mockConnection()即可。
方案二:重构原代码(长期最优解)
如果允许修改原代码,重构为测试友好的设计,避免依赖PowerMock这类字节码修改工具:
1. 提取接口封装连接逻辑
把Connection中的核心逻辑封装到接口,将私有方法改为接口的公共方法:
public interface ConnectionHandler { void handleConnection(); // 对应原来的privateMethodCall逻辑 }
2. 修改Connection类实现接口
去掉final修饰,把私有方法逻辑移到接口方法中,并添加测试用的注入入口:
public class Connection implements ConnectionHandler { private Connection() {} @Override public void handleConnection() { // 原来privateMethodCall的逻辑 } private static ConnectionHandler customHandler; // 测试用:注入自定义实现 public static void setCustomHandler(ConnectionHandler handler) { customHandler = handler; } public static void get() { if (customHandler != null) { customHandler.handleConnection(); return; } Connection conn = new Connection(); conn.handleConnection(); } }
3. 修改测试类
测试时注入Mock的ConnectionHandler,无需PowerMock:
import org.junit.Test; import org.mockito.Mockito; public class ManagerClassTest { @Test public void testPrepareForThing() { // 1. 创建Mock的ConnectionHandler ConnectionHandler mockHandler = Mockito.mock(ConnectionHandler.class); Connection.setCustomHandler(mockHandler); // 2. 执行测试 ManagerClass manager = new ManagerClass(); manager.prepareForThing(); // 3. 验证 Mockito.verify(mockHandler).handleConnection(); // 4. 清理(避免影响其他测试) Connection.setCustomHandler(null); } }
复用Mock
可以把Mock的ConnectionHandler实例和注入逻辑抽成测试工具类,方便所有测试复用。
方案对比
- PowerMock方案:无需修改生产代码,快速解决测试问题,但依赖字节码修改,可能和某些框架兼容不佳,长期维护成本略高。
- 重构方案:更符合面向接口的设计原则,测试更灵活,无额外工具依赖,是长期最优解,但需要修改生产代码。
内容的提问来源于stack exchange,提问作者Tyrannogyna
相关产品推荐
相关产品推荐

