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

查看

相关推荐
DigitalOcean8 天前
Laravel 开发者已在 DigitalOcean 上开通超过 10 万台服务器
前端·laravel
两个人的幸福10 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo12 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack12 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820713 天前
PHP 扩展——从入门到理解
php
鹏仔先生14 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下14 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip14 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒14 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog25014 天前
不要再继续优化 TCP
网络协议·tcp/ip·php