HTTP Callouts — DML Before Mock Callouts

DEX455 - Apex 集成与工具完全指南:Callouts、命名凭据、WSDL2Apex、Mock 测试

📄 第 392 页 🎬 视频课程

课程章节介绍

我们今天讲一个在 Apex 测试中很容易踩到的坑,就是“模拟调用”和“未提交工作”之间的小矛盾。大家记住一个简单的规则就行,我把原理也顺带讲一下。 你看,我们平时写测试类的时候,都会先做一些数据准备,比如插入一条客户记录、一个订单,这些操作在测试里都是被 Test Level Rollback 保护的,也就是说这些数据其实并没有真的写到数据库里,而是处在一种“待提交”的状态。这本身是好事,测试完自动回滚,不污染数据。 但是问题来了,如果你在测试里要模拟一个 HTTP 请求,比如调用外部 API,系统就特别严格:它不允许在还有未提交工作的情况下进行这个调用。因为模拟调用本质上是模拟一次出去的网络请求,如果这时候还有一堆没真正存进去的数据,系统怕出问题,就直接报错了。 所以解决办法很简单,就是让我们把数据准备和模拟调用分到不同的“执行区段”里去。具体怎么做呢? ,第一步,,所有需要插入数据的操作,都写在 `Test.startTest()` 之前。 比如你插入客户、插入业务记录,这些全都放前面。 ,第二步,,然后写 `Test.startTest();`。 ,第三步,,在 `startTest` 和 `stopTest` 之间,先通过 `Test.setMock(...)` 设置你要模拟的接口响应,然后写那段会触发调用的代码。注意,`Test.startTest()` 必须写在 `Test.setMock` 之前。 ,第四步,,最后 `Test.stopTest();`。 这样一来,`Test.startTest()` 就像一条分界线,它会让之前做的数据准备工作绑定成一个独立的已提交上下文,不再有未提交工作的尾巴。然后你在这个干净的环境里去模拟调用,就不会报错了。 另外强调一点:,数据准备的 DML 操作本身,绝对不要放到 `Test.startTest()` 和 `Test.stopTest()` 这个块里面,,不然跟直接写在模拟调用后面一样,还是会出问题。 这句话我简单总结一下,就是: ,“准备数据放前面,模拟调用放中间,mock 声明在 start 后,stop 之前全跑完。”, 不管是哪种模拟实现——`HttpCalloutMock` 接口、`StaticResourceCalloutMock` 还是 `MultiStaticResourceCalloutMock`,这个规则都一样适用。 你就记住这个顺序,测试就不会莫名其妙报“未提交工作”的错误了。

关键词

Apex Salesforce Developer Guide