如何统计Spock框架中两个不同Mock对象的交互次数?
Spock测试中多Mock对象的交互验证问题
需求:当AccountService.findUser抛出异常时,确保ReportService.reportRequest被调用一次。
示例业务代码
AccountService.java
@Getter @Setter @Service // others... public class AccountService { @Autowired private RestTemplate restTemplate; public Account findUser(String accountId) throws CustomInvalidExceptionA, CustomInvalidExceptionB { this.restTemplate.postForEntity(/* some params */); // other logics throw new CustomInvalidExceptionB("Parent Account does not qualify"); } }
SomeHelper.java
@Getter @Setter @Component // others... public class SomeHelper { @Autowired private InventoryService inventoryService; @Autowired private AccountService accountService; @Autowired private ReportService reportService; public ProcessRequest request(String accountId, Item item) { try { Account acct = this.accountService.findUser(accountId); this.inventoryService.check(item); } catch (CustomInvalidExceptionA | CustomInvalidExceptionB ex) { Report report = this.reportService.reportRequest(accountId, item, ex.getClass().getName()); log.error("ERROR" + ex.getClass()); //Other logics using report object } return null; } }
ReportService.java
@Getter @Setter @Service // others... public class ReportService { public Report reportRequest(String accountId, Item item, String message) { // some logics to make a report request return new Report(); } }
原测试代码的问题
你编写的测试存在几个核心问题:
class SomeHelperSpec extends Specification { SomeHelper helper; def setUp() { this.helper = new SomeHelper() this.helper.accountService = Mock(AccountService) this.helper.reportService = Mock(ReportService) } def "make sure if the accountService findUser failed, then reportService reportRequest is called once"() { given: this.helper.accountService.restTemplate = Mock(RestTemplate) this.helper.accountService.restTemplate.postForEntity(_ as URI, _, String) >> { throw new CustomInvalidExceptionA() } when: this.helper.accountService.findUser("123") // 错误:直接调用Mock对象方法,而非被测试类的业务入口 given: // 错误:违反Spock块顺序规则,不能重复使用given块 thrown CustomInvalidExceptionA 1 * this.helper.reportService.reportRequest(*_) >> _ } }
修正后的Spock测试代码
class SomeHelperSpec extends Specification { SomeHelper helper AccountService accountService = Mock() ReportService reportService = Mock() def setup() { helper = new SomeHelper() helper.accountService = accountService helper.reportService = reportService } def "当AccountService.findUser抛出异常时,ReportService.reportRequest应被调用一次"() { given: def accountId = "123" def item = new Item() // 直接让Mock的AccountService抛出目标异常,无需关注其内部实现 accountService.findUser(accountId) >> { throw new CustomInvalidExceptionB("Parent Account does not qualify") } when: helper.request(accountId, item) // 调用被测试类的业务方法 then: // 确认异常被内部捕获,未向外抛出 notThrown(CustomInvalidExceptionB) // 精确验证ReportService的方法调用次数与参数 1 * reportService.reportRequest(accountId, item, CustomInvalidExceptionB.class.getName()) >> new Report() } }
关键修正点
- 调用正确入口:必须调用
SomeHelper.request(),而非直接调用Mock对象的方法,确保触发完整业务逻辑。 - 简化Mock逻辑:直接Mock
AccountService.findUser抛出异常,无需深入其内部的RestTemplate调用,测试更聚焦业务规则。 - 遵循Spock规范:严格按照
given→when→then的块顺序编写,then块用于验证交互与结果。 - 精确参数匹配:避免使用模糊的
*_,明确匹配reportRequest的参数,保证测试严谨性。
内容的提问来源于stack exchange,提问作者Gwenda Thomas
相关产品推荐
相关产品推荐

