无需重构代码,用Spock/JUnit4测试Groovy时无法mock本地对象如何解决
测试解决方案
前置说明
你当前的代码是Groovy实现,Groovy原生支持构造方法Mock能力,无需修改原有业务代码即可完成全分支覆盖,以下分别提供Spock和JUnit4两种实现方案。
需覆盖的分支梳理
- 分支1:
env.findServiceByName("test-name")抛出异常,方法直接返回null - 分支2:成功获取
CfService实例,但service.getMap()中不存在prop.name键,调用toString()触发空指针异常 - 分支3:成功获取
CfService实例,且service.getMap()中存在prop.name对应的值,正常返回字符串结果
Spock 测试实现(推荐,Groovy生态原生适配)
Spock无需额外引入第三方Mock依赖,直接通过构造方法Mock即可实现:
import spock.lang.Specification import io.pivotal.cfenv.core.CfEnv import io.pivotal.cfenv.core.CfService class TestSpec extends Specification { def "测试findServiceByName抛出异常时返回null"() { given: "Mock CfEnv构造方法,模拟调用findServiceByName抛异常" def mockEnv = Mock(CfEnv) 1 * mockEnv.findServiceByName("test-name") >> { throw new RuntimeException("服务不存在") } GroovySpy(CfEnv, global: true) { new() >> mockEnv } def testObj = new Test() when: "调用getPropertyValue方法" def result = testObj.getPropertyValue() then: "验证返回结果为null" result == null } def "测试服务存在但属性不存在时触发空指针"() { given: "Mock CfEnv和CfService,返回无对应属性的Map" def mockService = Mock(CfService) 1 * mockService.getMap() >> [:] def mockEnv = Mock(CfEnv) 1 * mockEnv.findServiceByName("test-name") >> mockService GroovySpy(CfEnv, global: true) { new() >> mockEnv } def testObj = new Test() when: "调用getPropertyValue方法" testObj.getPropertyValue() then: "验证抛出空指针异常" thrown(NullPointerException) } def "测试正常场景下返回对应属性值"() { given: "Mock CfEnv和CfService,返回带目标属性的Map" def mockService = Mock(CfService) 1 * mockService.getMap() >> ["prop.name": "test-value"] def mockEnv = Mock(CfEnv) 1 * mockEnv.findServiceByName("test-name") >> mockService GroovySpy(CfEnv, global: true) { new() >> mockEnv } def testObj = new Test() when: "调用getPropertyValue方法" def result = testObj.getPropertyValue() then: "验证返回结果正确" result == "test-value" } }
JUnit4 测试实现
JUnit4需要配合PowerMock实现构造方法Mock,需先引入PowerMock相关依赖,测试代码如下:
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 import io.pivotal.cfenv.core.CfEnv import io.pivotal.cfenv.core.CfService import static org.junit.Assert.* import static org.mockito.ArgumentMatchers.anyString @RunWith(PowerMockRunner.class) @PrepareForTest([Test.class, CfEnv.class, CfService.class]) class TestJUnit4Test { @Test void 测试findServiceByName抛出异常时返回null() { // Mock CfEnv构造 CfEnv mockEnv = PowerMockito.mock(CfEnv.class) PowerMockito.whenNew(CfEnv.class).withNoArguments().thenReturn(mockEnv) PowerMockito.when(mockEnv.findServiceByName(anyString())).thenThrow(new RuntimeException("服务不存在")) Test testObj = new Test() String result = testObj.getPropertyValue() assertNull(result) } @Test(expected = NullPointerException.class) void 测试服务存在但属性不存在时触发空指针() { // Mock CfEnv和CfService CfService mockService = PowerMockito.mock(CfService.class) PowerMockito.when(mockService.getMap()).thenReturn(Collections.emptyMap()) CfEnv mockEnv = PowerMockito.mock(CfEnv.class) PowerMockito.whenNew(CfEnv.class).withNoArguments().thenReturn(mockEnv) PowerMockito.when(mockEnv.findServiceByName(anyString())).thenReturn(mockService) Test testObj = new Test() testObj.getPropertyValue() } @Test void 测试正常场景下返回对应属性值() { // Mock CfEnv和CfService CfService mockService = PowerMockito.mock(CfService.class) Map<String, Object> propMap = new HashMap<>() propMap.put("prop.name", "test-value") PowerMockito.when(mockService.getMap()).thenReturn(propMap) CfEnv mockEnv = PowerMockito.mock(CfEnv.class) PowerMockito.whenNew(CfEnv.class).withNoArguments().thenReturn(mockEnv) PowerMockito.when(mockEnv.findServiceByName(anyString())).thenReturn(mockService) Test testObj = new Test() String result = testObj.getPropertyValue() assertEquals("test-value", result) } }
注意:原有代码中实例化的
CfCredentials creds未参与任何逻辑计算,无需Mock,不影响测试结果。
内容的提问来源于stack exchange,提问作者Vaishnavi Killekar
相关产品推荐
相关产品推荐

