You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何统计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逻辑:直接MockAccountService.findUser抛出异常,无需深入其内部的RestTemplate调用,测试更聚焦业务规则。
  • 遵循Spock规范:严格按照given→when→then的块顺序编写,then块用于验证交互与结果。
  • 精确参数匹配:避免使用模糊的*_,明确匹配reportRequest的参数,保证测试严谨性。

内容的提问来源于stack exchange,提问作者Gwenda Thomas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 15:08:16