当前位置: 首页 > news >正文

使用 Laravel 中的自定义存根简化工作

在开发与外部服务、API 或复杂功能交互的应用程序时,测试几乎总是很困难。简化测试的一种方法是使用存根类。以下是我通常使用它们的方法。

福利简介
存根是接口或类的伪实现,用于模拟真实服务的行为。它们允许您:

无需调用外部服务即可测试代码

无需 API 密钥即可在本地工作

通过避免昂贵的 API 调用来加速测试

创建可预测的测试场景

外部会计服务示例
让我们看一个外部会计服务的简单接口。实际上,你甚至不需要接口来实现这一点,但它可以更轻松地切换实现并保持同步。

interface ExternalAccountingInterface
{public function createRecord(array $data): string;
}

以下是调用外部 API 的实际实现:

class ExternalAccounting implements ExternalAccountingInterface
{public function __construct(private readonly HttpClient $client,private readonly string $apiKey,) {}public function createRecord(array $data): string{$response = $this->client->post("https://api.accounting-service.com/v1/records", ['headers' => ['Authorization' => "Bearer {$this->apiKey}",'Content-Type' => 'application/json',],'json' => $data,]);$responseData = json_decode($response->getBody(), true);return $responseData['record_id'];}
}

现在,这里有一个用于测试的虚假实现:

class FakeExternalAccounting implements ExternalAccountingInterface
{private array $createdRecords = [];private bool $hasEnoughCredits = true;public function createRecord(array $data): string{if (! $this->hasEnoughCredits) {throw new InsufficientCreditsException("Not enough credits to create a record");}$recordId = Str::uuid();$this->createdRecords[$recordId] = $data;return $recordId;}// Edge case simulationpublic function withNotEnoughCredits(): self{$this->hasEnoughCredits = false;return $this;}// Helper methods for assertionspublic function assertRecordsCreated(array $eventData): void{Assert::assertContains($eventData,$this->createdRecords,'Failed asserting that the record was created with the correct data.');}public function assertNothingCreated(): void{Assert::assertEmpty($this->createdRecords, 'Records were created unexpectedly.');}
}

之前和之后:重构以使用存根
之前:使用 Mockery

public function testCreateAccountingRecord(): void
{// Create a mock using Mockery$accountingMock = $this->mock(ExternalAccountingInterface::class);// Set expectations$accountingMock->shouldReceive('createRecord')->once()->with(Mockery::on(function ($data) {return isset($data['type']) && $data['type'] === 'invoice' &&isset($data['amount']) && $data['amount'] === 99.99;}))->andReturn('rec_123456');// Bind the mock$this->swap(ExternalAccountingInterface::class, $accountingMock);// Execute the test$response = $this->post('/api/invoices', ['product_id' => 'prod_123','amount' => 99.99,]);// Assert the response$response->assertStatus(200);$response->assertJson(['success' => true]);
}

之后:使用存根

public function testCreateAccountingRecord(): void
{// Create an instance of our custom stub$fakeAccounting = new FakeExternalAccounting;// Bind the stub$this->swap(ExternalAccountingInterface::class, $fakeAccounting);// Execute the test$response = $this->post('/api/invoices', ['product_id' => 'prod_123','amount' => 99.99,]);// Assert the response$response->assertStatus(200);$response->assertJson(['success' => true]);// Assert that records were created with the expected data$fakeAccounting->assertRecordsCreated(['type' => 'invoice','amount' => 99.99,]);
}

自定义存根可以轻松测试边缘情况和错误场景:

public function testInvoiceFailsWhenNotEnoughCredits(): void
{// Create an instance of our custom stub$fakeAccounting = new FakeExternalAccounting;// Configure the stub to simulate not enough credits$fakeAccounting->withNotEnoughCredits();// Bind the stub$this->swap(ExternalAccountingInterface::class, $fakeAccounting);// Execute the test expecting a failure$response = $this->post('/api/invoices', ['product_id' => 'prod_123','amount' => 99.99,]);// Assert the response handles the failure correctly$response->assertStatus(422);$response->assertJson(['error' => 'Insufficient credits']);// Assert that no records were created$fakeAccounting->assertNothingCreated();
}

通过此设置,您的本地开发环境将使用虚假实现,让您无需 API 密钥即可工作,也不用担心速率限制。当部署到暂存区或生产环境时,应用程序将使用真实的实现

查看

http://www.lqws.cn/news/201115.html

相关文章:

  • 计算机组成与体系结构:补码数制二(Complementary Number Systems)
  • 数据表自增主键命名规范
  • STM32学习之I2C(理论篇)
  • 【C++字符串基础解析1】
  • 网络原理5 - TCP4
  • C# 中替换多层级数据的 Id 和 ParentId,保持主从或父子关系不变
  • MySQL中的内置函数
  • 统一点云数据格式:高效转换与属性保留
  • 自适应长度惩罚强化学习的高效推理
  • Conda环境备份教程
  • Spring事务和事务传播机制
  • 隐私计算时代B端页面安全设计:数据脱敏与权限体系升级路径
  • 力扣面试150题--除法求值
  • 【力扣】2434.使用机器人打印字典序最小的字符串
  • 实战二:开发网页端界面完成黑白视频转为彩色视频
  • 腾讯开源视频生成工具 HunyuanVideo-Avatar,上传一张图+一段音频,就能让图中的人物、动物甚至虚拟角色“活”过来,开口说话、唱歌、演相声!
  • 微前端 - Native Federation使用完整示例
  • 计算机是如何⼯作的
  • 【Linux shell】shell中的变量——构建脚本逻辑的基石
  • qt使用笔记二:main.cpp详解
  • PostgreSQL 的扩展pageinspect
  • 基于Python学习《Head First设计模式》第八章 模板方法模式
  • 基于Python学习《Head First设计模式》第七章 适配器和外观模式
  • moon服务器引擎-协议生成报错
  • 意识上传伦理前夜:我们是否在创造数字奴隶?
  • Scade 语言概念 - 方程(equation)
  • 1990-2023年 地级市人工智能企业数量-社科经管实证数据
  • Linux 文件系统与 I/O 编程核心原理及实践笔记
  • Python Cookbook-7.12 在 SQLite 中储存 BLOB
  • 华为云Flexus+DeepSeek征文|Dify - LLM 云服务单机部署大语言模型攻略指南