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

查看

相关推荐
码云数智-大飞14 小时前
PHP 接口开发规范:统一返回格式、异常处理与参数校验
开发语言·php
dadaobusi15 小时前
仿真边界处理
开发语言·php
微学AI17 小时前
内网穿透的应用-把飞牛NAS变成家庭录像中心:EasyNVR部署与远程回放实战
开发语言·php
峥嵘life17 小时前
Android WiFi连接过程 wpa_supplicant 日志分析
android·开发语言·php
weixin_BYSJ198718 小时前
「课设设计」springboot校园超市助购系统26449 (附源码)
java·javascript·spring boot·python·django·flask·php
xuefeiniao18 小时前
CentOS + 宝塔服务器偶发 SSH、面板、网站全部无法访问排查记录(PHP-FPM 内存耗尽案例)
服务器·centos·ssh·php
神州世通1 天前
解密企业通信安全防线:Avaya Aura 三层安全架构深度解析
开发语言·php
AC赳赳老秦1 天前
企业工商公开信息采集分析:OpenClaw 批量查询企业工商信息,生成企业画像报告
大数据·开发语言·python·自动化·php·deepseek·openclaw
AC赳赳老秦2 天前
司法公开数据采集应用:OpenClaw 抓取裁判文书公开信息,批量整理同类参考案例
java·大数据·python·数据挖掘·数据分析·php·openclaw
Vae_Mars2 天前
C#中多线程异步操作的方法-Parallel.ForEachAsync
开发语言·c#·php