Java编写单元测试时如何mock类中特定私有方法的返回响应
Java中mock私有方法有两种成熟实现方案,先做个前置提醒:如果somePrivateMethod的逻辑独立可抽离,优先建议把它下沉到单独的依赖类,通过依赖注入的方式做常规mock,这种方式不需要侵入类的内部实现,测试用例也不会因为私有方法重构(比如改方法名、调整参数)意外失败。如果代码重构成本较高,必须直接mock私有方法,可以参考下面两种实现:
具体实现方案
方案1:Mockito 3.4.0及以上版本原生实现
高版本Mockito不需要额外引入第三方组件,只要开启inline mock maker即可支持私有方法打桩。开启方式很简单:在测试资源目录src/test/resources/mockito-extensions/下新建名为org.mockito.plugins.MockMaker的文件,文件内容写mock-maker-inline就完成配置。
JUnit5环境下的测试代码示例:
import org.junit.jupiter.api.Test; import org.mockito.Spy; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import java.lang.reflect.Method; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; @ExtendWith(MockitoExtension.class) public class FooTest { // 用@Spy生成被测类实例,保留真实方法逻辑 @Spy private Foo foo; @Test public void testGetData_withGoodJson() throws Exception { // 反射获取目标私有方法 Method privateMethod = Foo.class.getDeclaredMethod("somePrivateMethod", String.class); privateMethod.setAccessible(true); // 构造预设返回值 JsonObject goodJson = new JsonObject(); goodJson.addProperty("1", 2); // 私有方法打桩:任意字符串入参都返回goodJson doReturn(goodJson).when(foo).method(privateMethod).withArguments(anyString()); // 调用被测方法执行断言 String result = foo.getData("testId"); // 自行补充断言逻辑 } @Test public void testGetData_withBadJson() throws Exception { Method privateMethod = Foo.class.getDeclaredMethod("somePrivateMethod", String.class); privateMethod.setAccessible(true); JsonObject badJson = new JsonObject(); badJson.addProperty("foo", "bar"); doReturn(badJson).when(foo).method(privateMethod).withArguments(anyString()); String result = foo.getData("testId"); // 自行补充断言逻辑 } }
方案2:PowerMock实现(兼容低版本Mockito、JUnit4场景)
如果项目使用JUnit4或者Mockito版本低于3.4,用PowerMock做字节码增强即可实现私有方法mock,是老项目最常用的方案:
- 先引入和当前Mockito版本匹配的PowerMock依赖,核心依赖为
powermock-api-mockito2、powermock-module-junit4 - 测试类上必须添加
@RunWith(PowerMockRunner.class)和@PrepareForTest(Foo.class)注解,后者用于标记需要做字节码修改的目标类
测试代码示例:
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.mockito.ArgumentMatchers.anyString; import static org.powermock.api.mockito.PowerMockito.*; @RunWith(PowerMockRunner.class) @PrepareForTest(Foo.class) public class FooTest { @Test public void testGetData_withGoodJson() throws Exception { // 生成spy实例,保留真实方法逻辑 Foo foo = spy(new Foo()); JsonObject goodJson = new JsonObject(); goodJson.addProperty("1", 2); // 直接通过方法名给私有方法打桩,不需要手动写反射逻辑 doReturn(goodJson).when(foo, "somePrivateMethod", anyString()); String result = foo.getData("testId"); // 自行补充断言逻辑 } @Test public void testGetData_withBadJson() throws Exception { Foo foo = spy(new Foo()); JsonObject badJson = new JsonObject(); badJson.addProperty("foo", "bar"); doReturn(badJson).when(foo, "somePrivateMethod", anyString()); String result = foo.getData("testId"); // 自行补充断言逻辑 } }
注意:两个方案都使用
spy而非mock创建被测类实例,原因是spy会保留类中所有非打桩方法的真实逻辑,仅替换我们指定的私有方法返回值,完全符合单元测试的验证目标。如果需要针对不同入参返回不同结果,把打桩时的参数匹配器anyString()替换为具体的id值即可。
内容的提问来源于stack exchange,提问作者frazman
相关产品推荐
相关产品推荐

