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

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

福利简介

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

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

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

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

创建可预测的测试场景

外部会计服务示例

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

php 复制代码
interface ExternalAccountingInterface
{
    public function createRecord(array $data): string;
}

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

php 复制代码
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'];
    }
}

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

php 复制代码
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 simulation
    public function withNotEnoughCredits(): self
    {
        $this->hasEnoughCredits = false;
        return $this;
    }

    // Helper methods for assertions
    public 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

php 复制代码
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]);
}

之后:使用存根

php 复制代码
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,
    ]);
}

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

php 复制代码
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 密钥即可工作,也不用担心速率限制。当部署到暂存区或生产环境时,应用程序将使用真实的实现

查看

相关推荐
专注前端30年6 小时前
【PHP开发与安全防护实战】性能调优手册
android·安全·php
oMcLin6 小时前
如何在 RHEL 7 上优化 Nginx 与 PHP‑FPM 配置,确保高并发 Web 应用的稳定性与响应速度?
前端·nginx·php
IT=>小脑虎10 小时前
PHP零基础衔接进阶知识点【详解版】
开发语言·学习·php
xifangge202511 小时前
PHP 接口跨域调试完整解决方案附源码(从 0 到定位问题)
开发语言·php
ICT董老师11 小时前
通过kubernetes部署nginx + php网站环境
运维·nginx·云原生·容器·kubernetes·php
bleach-12 小时前
buuctf系列解题思路祥讲--[SUCTF 2019]CheckIn1--文件上传以及user.ini的应用
nginx·web安全·网络安全·php
BingoGo13 小时前
免费可商用商业级管理后台 CatchAdmin V5 正式发布 插件化与开发效率的全面提升
vue.js·后端·php
AI 智能服务1 天前
第6课__本地工具调用(文件操作)
服务器·人工智能·windows·php
松涛和鸣1 天前
49、智能电源箱项目技术栈解析
服务器·c语言·开发语言·http·html·php
晚枫歌F1 天前
io_uring的介绍和实现
开发语言·php