ohseesoftware/laravel-veneer

Laravel Veneer 提供了一系列固定数据(fixture)和模拟方法,帮助您覆盖 80% 的第三方服务模拟。

v1.4.0 2022-01-13 14:56 UTC

This package is auto-updated.

Last update: 2024-09-13 20:42:18 UTC


README

一套固定数据和模拟方法,帮助您轻松模拟第三方服务。

概述

Laravel Veneer 旨在解决开发者编写测试时面临的两个问题

  1. 提供第三方 API 的静态响应数据(固定数据)
  2. Mockery 之上提供 SDK 特定的抽象,用于模拟 SDK 调用

这两个目标旨在帮助完成 80% 的模拟/API 响应工作,而将剩余的 20% 交由开发者完成。例如,Laravel Veneer 将提供创建新日历的通用模拟和固定数据,但如果你需要测试特定的响应,你需要自己进行模拟。

固定数据

固定数据是从第三方 API(如 Twitter、GitHub、Cronofy 等)获取的静态响应数据。该包的目标之一是允许社区为任何可用的 API 贡献响应固定数据。

SDK 模拟

SDK 模拟层专门为 Laravel 构建,建立在 Mockery 之上。它允许开发者在使用 SDK 包时快速模拟各种第三方 API 的 SDK 调用。

安装

使用 composer 安装该包

composer require ohseesoftware/laravel-veneer

没有服务提供者或其他注册项,所有类都直接用于测试类中。

用法

该包的目标是使 80% 的模拟用例变得简单。涉及三个步骤

  1. 创建一个 MockProvider 实例
  2. 将您想模拟的方法添加到 MockProvider
  3. 应用模拟

最简单的例子是一行代码

$this->veneer(CronofyMock::make()->add(CreateChannelMock::make()));

其中我们

  • 创建一个新的 MockProvider 实例:CronofyMock::make()
  • 添加要模拟的方法:->add(CreateChannelMock::make())
  • 使用公开的特性应用模拟:$this->veneer(...)

这是一个使用该包模拟 Cronofy 的 createChannel 方法的完整测试类示例

<?php

use OhSeeSoftware\LaravelVeneer\Providers\Cronofy\CreateChannelMock;
use OhSeeSoftware\LaravelVeneer\Providers\Cronofy\CronofyMock;
use OhSeeSoftware\LaravelVeneer\Traits\VeneerMocks;

class MyTest extends TestCase
{
    use VeneerMocks;
    
    public function testItDoesSomething()
    {
        // Given, mock the 3rd party SDK
        $this->veneer(CronofyMock::make()->add(CreateChannelMock::make()));

        // When, call your code that will call the 3rd party SDK
        $this->post('/channel');

        // Then, assert your code handled the 3rd party response correctly
        $this->assertDatabaseHas('channels', [
            'cronofy_channel_id' => 'chn_0000000000'
        ]);
    }
}

如果您想知道硬编码的 chn_0000000000 从哪里来,它来自 固定数据,该数据已被定义。

使用特性

VeneerMocks 特性定义了一个方法,该方法将给定的提供者上的当前 $application 实例设置为当前实例,然后调用 mock 方法。该 mock 方法将通过 Laravel 的 $this->partialMock 方法将模拟的方法作为一个部分模拟应用。

覆盖模拟响应

如果您对默认的固定数据响应不满意,您可以自己覆盖它

$this->veneer(
    CronofyMock::make()->add(
        CreateChannelMock::make()->return('Hello world!')
    )
);

现在,当调用 createChannel 方法时,它将返回 'Hello world!' 而不是默认的固定数据。

合并模拟响应与您的数据

好吧,现在假设您只想调整固定数据的一个部分,比如返回的频道 ID。您可以通过 merge($key, $value) 方法完成

$this->veneer(
    CronofyMock::make()->add(
        CreateChannelMock::make()->merge('channel.channel_id', 'chn_123456')
    )
);

现在,当调用 createChannel 方法时,它将返回默认的固定数据,但 channel.channel_id 的值将设置为 chn_123456,这意味着您的测试现在看起来像

public function testItDoesSomething()
{
    // Given, mock the 3rd party SDK with a custom `channel.channel_id` value
    $this->veneer(
        CronofyMock::make()->add(
            CreateChannelMock::make()->merge('channel.channel_id', 'chn_123456')
        )
    );

    // When, call your code that will call the 3rd party SDK
    $this->post('/channel');

    // Then, assert your code handled the 3rd party response correctly
    $this->assertDatabaseHas('channels', [
        'cronofy_channel_id' => 'chn_123456'
    ]);
}

预期参数

您可以通过使用 with(...$args) 方法来利用 Mockery 的 withArgs 方法

$this->veneer(
    CronofyMock::make()->add(
        CreateChannelMock::make()->with('test')
    )
);

当模拟的方法被调用时,它将验证是否将值 test 传递给了该方法。如果未传递 test,则测试将失败。

多次调用模拟方法

默认情况下,Laravel Veneer 预期所有模拟方法只被调用一次。但是,如果您需要将方法模拟多次调用,可以使用 times(int $times) 方法

$this->veneer(
    CronofyMock::make()->add(
        CreateChannelMock::make()->times(3)
    )
);

在上面的例子中,如果模拟方法不是精确地调用 3 次,则测试将失败。

贡献

以下部分将概述贡献新固定值和 SDK 模拟的指南。

贡献固定值

在添加固定值时,请尽量遵循以下指南。您可以查看 现有固定值 以了解它们的结构。

格式

目前,Laravel Veneer 预期所有固定值都定义在 JSON 中。这可能在将来发生变化,但就初始工作而言,我们希望专注于 JSON 端点。

文件夹结构

固定值的文件夹结构根据固定值是针对 HTTP 响应还是传入的 webhook 负载而略有不同。

对于 HTTP 响应,指南是

/fixtures/{name_of_service}/responses/{version?}/{path}/{method}.json

其中

  • name_of_service 是固定值所属服务的名称
  • version 只在 responses 中需要,如果适用,应设置为 API 的版本
  • path 是端点的路径
  • method 是用于调用端点的 HTTP 方法

例如,以下是通过 Twitter 的 v2 API 创建新推文的路径

/fixtures/twitter/responses/v2/tweets/post.json

对于 webhook 负载固定值,指南要简单一些

/fixtures/{name_of_service}/webhooks/{event}.json

其中

  • name_of_service 是固定值所属服务的名称
  • event 是触发 webhook 的事件的名称

例如,以下是 Cronofy 的 changeNotification webhook 负载 的路径

/fixtures/cronofy/webhooks/changeNotification.json

贡献 SDK 模拟

对于添加新的 SDK 模拟,有两个方面

  1. 添加一个新的类来定义您要模拟的类,我们称它为 MockProvider
  2. 添加一个新的类来模拟您要模拟的方法,我们称它为 MockedMethod

添加新的 MockProvider

MockProvider 的使用相当简单。您只需要扩展 MockProvider 类,然后实现一个方法告诉 Laravel Veneer 您要模拟哪个类

<?php

namespace OhSeeSoftware\LaravelVeneer\Providers\Cronofy;

use OhSeeSoftware\LaravelVeneer\Providers\MockProvider;

class CronofyMock extends MockProvider
{
    /**
     * FQDN of the class being mocked.
     */
    public function mockedClass(): string
    {
        // Laravel Veneer will apply a partial mock to the `\Cronofy\Cronofy` class
        return \Cronofy\Cronofy::class;
    }
}

添加新的 MockedMethod

添加新的 MockedMethod 也相当简单,但允许更多的配置。您需要创建一个新的类,该类扩展了 MockedMethod 类,并实现所需的方法

<?php

namespace OhSeeSoftware\LaravelVeneer\Providers\Cronofy\Channels;

use OhSeeSoftware\LaravelVeneer\Providers\MockedMethod;

class ListChannelsMock extends MockedMethod
{
    public function method(): string
    {
        /**
         * Name of the method being mocked.
         */
        return 'listChannels';
    }
}

默认情况下,唯一需要实现的方法是 method(): string,它告诉 Laravel Veneer 您要模拟哪个 MockProvider 类的方法。

如果您希望您的模拟方法从固定值返回数据,通过 fixturePath(): ?string 定义固定值的路径

/**
 * Path to the fixture data file, if applicable.
 *
 * The value should be a relative path from the `fixtures` directory.
 */
public function fixturePath(): ?string
{
    return 'cronofy/responses/v1/channels/index.json';
}

如果您需要您的模拟方法返回完全定制的某些内容(例如不同类的新的实例等),您可以覆盖 result() 方法

/**
 * The result returned from the mocked method.
 */
public function result()
{
    return 'hello world!'; // Return 'hello world!' whenever this mocked method is called
}