ohseesoftware / laravel-veneer
Laravel Veneer 提供了一系列固定数据(fixture)和模拟方法,帮助您覆盖 80% 的第三方服务模拟。
Requires
- php: >=8.0
- illuminate/contracts: ^8.0
- illuminate/support: ^8.0
- illuminate/testing: ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.4
- mockery/mockery: ^1.4
- orchestra/testbench: ^6.7
- php-coveralls/php-coveralls: ^2.4
- phpunit/phpunit: ^9.5
README
一套固定数据和模拟方法,帮助您轻松模拟第三方服务。
概述
Laravel Veneer 旨在解决开发者编写测试时面临的两个问题
- 提供第三方 API 的静态响应数据(固定数据)
- 在 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% 的模拟用例变得简单。涉及三个步骤
- 创建一个
MockProvider实例 - 将您想模拟的方法添加到
MockProvider - 应用模拟
最简单的例子是一行代码
$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 模拟,有两个方面
- 添加一个新的类来定义您要模拟的类,我们称它为
MockProvider - 添加一个新的类来模拟您要模拟的方法,我们称它为
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 }