能否Mock Gradle Project方法?Gradle插件单元测试遇阻
问题场景
编写Gradle插件单元测试时,尝试通过Mockito Spy Project实例来MockhasProperty和property方法,验证扩展的outputDir属性是否能正确读取项目属性,但断言失败。调试发现扩展内部使用的是原始Project实例而非Mock后的实例。
插件中扩展的核心代码:
this.outputDir = project.getObjects().property(String.class); this.outputDir.convention(project.provider(() -> { if (project.hasProperty("com.example.outputDir")) { return project.property("com.example.outputDir").toString(); } return "./default-output"; }));
测试代码:
// 创建Gradle Project实例 ProjectBuilder builder = ProjectBuilder.builder(); // Mock Project(Project是接口) Project testProject = Mockito.spy(builder.build()); // 向Mock后的项目应用插件 testProject.getPlugins().apply(PLUGIN_NAME); // 启用Mock when(testProject.hasProperty("com.example.outputDir")).thenReturn(true); // 此断言生效 Assertions.assertEquals(true, testProject.hasProperty("com.example.outputDir")); // 此断言失败 MyExtension ext = (MyExtension) testProject.getExtensions().getByName("my-extension"); Assertions.assertEquals("hello-world", ext.outputDir.get());
问题原因
ProjectBuilder构建的真实Project实例内部包含大量关联组件(如ExtensionContainer、ObjectFactory等),这些组件直接持有原始Project的引用。当你通过Mockito Spy包装这个实例时,插件应用过程中扩展内部捕获的Project引用是原始实例,而非Spy代理,导致Mock的方法无法生效。
解决方案
方案1:使用ProjectBuilder直接配置属性(推荐)
Gradle官方推荐使用ProjectBuilder创建测试用Project,直接通过withProperty方法设置属性,无需Mock复杂的Project接口,测试更贴近真实场景:
// 创建携带目标属性的Project实例 Project testProject = ProjectBuilder.builder() .withProperty("com.example.outputDir", "hello-world") .build(); // 应用插件 testProject.getPlugins().apply(PLUGIN_NAME); // 获取扩展并断言 MyExtension ext = testProject.getExtensions().getByType(MyExtension.class); Assertions.assertEquals("hello-world", ext.outputDir.get());
这种方式完全利用Gradle提供的测试工具,避免了Mock带来的复杂问题,测试结果更可靠。
方案2:完全Mock Project及依赖组件(不推荐)
如果必须使用Mock,需要手动Mock Project的所有依赖组件,确保扩展能获取到Mock后的实例。这种方式需要Mock大量Gradle内部接口,实现复杂且容易遗漏:
// 完全Mock Project接口 Project testProject = Mockito.mock(Project.class); ObjectFactory objectFactory = Mockito.mock(ObjectFactory.class); Property<String> outputDirProp = Mockito.mock(Property.class); ExtensionContainer extensionContainer = Mockito.mock(ExtensionContainer.class); MyExtension testExtension = new MyExtension(testProject); // 配置Mock行为 when(testProject.getObjects()).thenReturn(objectFactory); when(objectFactory.property(String.class)).thenReturn(outputDirProp); when(testProject.hasProperty("com.example.outputDir")).thenReturn(true); when(testProject.property("com.example.outputDir")).thenReturn("hello-world"); when(testProject.getExtensions()).thenReturn(extensionContainer); when(extensionContainer.getByName("my-extension")).thenReturn(testExtension); when(extensionContainer.getByType(MyExtension.class)).thenReturn(testExtension); // 应用插件(需确保插件代码依赖可注入) testProject.getPlugins().apply(PLUGIN_NAME); // 断言 Assertions.assertEquals("hello-world", testExtension.outputDir.get());
注意:这种方式要求插件的扩展构造器能接收Project参数,且插件内部创建扩展时使用传入的Project实例,否则仍无法生效。
总结
Gradle的Project接口设计复杂,Mock整个接口容易引发各种隐藏问题,优先使用ProjectBuilder配置测试用例是更稳妥的选择。
内容的提问来源于stack exchange,提问作者marverix

