Spock测试中修改方法内部变量及验证else分支方法执行的实现方式
在Spock测试中修改方法内部变量并验证else分支执行
针对你的需求,我分两部分来解答:修改方法内部的局部变量,以及确保else分支里的方法被执行并验证它们的调用。
一、修改方法内部的局部变量(uName或uCat)
方法内部的局部变量(比如uName、uCat)默认无法直接从测试代码修改,因为它们的作用域仅限于userComment()方法内部。不过我们可以通过重构代码或者结合Groovy元编程/字节码工具来实现需求,推荐优先用重构的方式(更易维护):
方案1:重构代码,让变量可配置/可覆盖
把局部变量的来源改成可注入或可覆盖的方法,这样测试时可以轻松替换值:
class UserService { // 把uName的获取改成可覆盖的方法 protected String getUName() { return "test123" } void userComment() { def uName = getUName() def uCat = Category.getUserCategory(uName) if (uCat.empty){ println("Category is empty") } else { postComment(); sendEmails(); } } void postComment() { /* 业务实现 */ } void sendEmails() { /* 业务实现 */ } }
然后在Spock测试中,用Spy来覆盖getUName()方法,返回你想要的值:
def "修改uName为John并触发else分支"() { given: def userService = Spy(UserService) { // 覆盖getUName方法,返回"John" getUName() >> "John" } // 全局mock Category的静态方法,返回非空集合触发else分支 GroovyMock(Category, global: true) Category.getUserCategory("John") >> ["some-category"] when: userService.userComment() then: // 验证else分支的方法被调用 1 * userService.postComment() 1 * userService.sendEmails() }
如果要直接修改uCat的值,只需mockCategory.getUserCategory()的返回值即可,比如在另一个测试中让它返回包含"seller"的集合:
def "修改uCat为seller并测试"() { given: def userService = Spy(UserService) GroovyMock(Category, global: true) // 让getUserCategory返回包含"seller"的非空集合 Category.getUserCategory(_) >> ["seller"] when: userService.userComment() then: 1 * userService.postComment() 1 * userService.sendEmails() }
方案2:用PowerMock直接修改局部变量(不推荐,除非无法重构)
如果实在不能修改原代码,可以用PowerMock结合Spock来修改方法内部的局部变量。不过这种方式依赖字节码操作,可读性和维护性较差:
@RunWith(PowerMockRunner) @PrepareForTest(UserService) class UserServiceSpec extends Specification { def "直接修改uName局部变量"() { given: def userService = new UserService() GroovyMock(Category, global: true) Category.getUserCategory(_) >> ["seller"] when: // 使用PowerMock的Whitebox修改局部变量uName为"John" def method = UserService.getDeclaredMethod("userComment") Whitebox.invokeMethod(userService, method, new LocalVariable("uName", String.class, 0), "John") then: 1 * userService.postComment() 1 * userService.sendEmails() } }
注意:PowerMock的使用会增加测试复杂度,尽量优先选择重构方案。
二、确保else分支的postComment和sendEmails被执行并验证
要让else分支执行,核心是让uCat.empty为false,也就是让Category.getUserCategory(uName)返回一个非空的集合。然后用Spock的Spy或Mock来验证这两个方法是否被调用:
完整的测试示例
import spock.lang.Specification import spock.lang.Spy import groovy.mock.interceptor.GroovyMock class UserServiceSpec extends Specification { def "验证else分支的postComment和sendEmails被执行"() { given: // 创建UserService的Spy,跟踪真实对象的方法调用 @Spy UserService userService = new UserService() // 全局mock Category的静态方法,返回非空集合触发else分支 GroovyMock(Category, global: true) Category.getUserCategory(_) >> ["valid-category"] when: userService.userComment() then: // 验证postComment和sendEmails各被调用一次 1 * userService.postComment() 1 * userService.sendEmails() // 可选:确保if分支的println没有被执行 0 * println(_) } }
关键点说明
- 使用
GroovyMock全局mockCategory类的静态方法,让它返回非空值,从而触发else分支。 - 使用
Spy包装UserService实例,既可以保留原方法的逻辑,又能跟踪方法调用情况。 - 在
then块中用1 * methodName()来验证方法被调用的次数,确保else分支的逻辑被执行。
内容的提问来源于stack exchange,提问作者CChang




