使用 Wiremock 在 Laravel 测试中模拟 API使用教程
Laravel  /  管理员 发布于 1年前   298
测试时对应用程序接口(API)进行核算是一个令人困惑、有时甚至是痛苦的过程。
无论是测试客户端组件还是服务器端代码,都必须在代码中以某种方式模拟或伪造请求和响应。
第三方 HTTP 客户端代码的测试工具鱼龙混杂。
在 Laravel 生态系统中,我们有幸在 Laravel HTTP 客户端中内置了测试功能,
但对于不使用 Laravel 客户端的 HTTP 客户端,你该怎么办呢?
软件包可以直接使用 Guzzle 或 cURL,也可能不包含某种方法来轻松伪造 API 调用。
我首先想到的是将原始 HTTP 客户端封装到服务中,然后模拟/伪造响应:
use App\Service;
use Mockery\MockInterface;
$mock = $this->mock(Service::class, function (MockInterface $mock) {
$test_products = $this->getTestProducts();
$mock->shouldReceive('getProducts')
->once()
->andReturn($test_products);
});
你可能见过有人出于同样的目的引入 Guzzle 中间件,或者更糟糕的是,直接嘲笑 Guzzle。
虽然通过服务来模拟代码以实现简单的 API 调用可能是你所需要的全部,
但如果你的应用程序中有更复杂的 API 使用,你就不能再在代码中模拟 HTTP 客户端了,
而应该使用 HTTP 模拟服务器。
进入 Wiremock。
https://wiremock.org/
市场上有各种 HTTP 模拟服务器,但我们将特别关注 Wiremock,它是一个基于 Java 的 HTTP 模拟库。它包括面向 Java 开发人员的第一方工具。
不过,它还提供了一个独立的运行时(JAR)和 REST API,我们可以在 PHP 项目中使用。
我们将对 Wiremock 的所有功能进行浅尝辄止的介绍,
但我们会在今后跟进更多教程,深入探讨你会喜欢的功能。
入门使用
为了深入了解 Wiremock,我们将创建一个新的 Laravel 应用程序,
并通过 Docker 运行 Wiremock:
laravel new --git --pest --no-interaction wiremock-demo
cd wiremock-demo
如果你正在跟进,也可以使用 PHPUnit;
我们的代码示例在应用过程中很容易转换。
接下来,我们将创建一个 docker-compose.yaml 文件,在项目根目录下为默认的 Wiremock 映射创建一个空目录
(稍后我们将介绍这些映射):
touch docker-compose.yaml
mkdir -p tests/Feature/resources/wiremock
下面就是我们的 Docker Compose 文件:
version: "3.8"
services:
wiremock:
image: wiremock/wiremock
ports:
- "8080:8080"
volumes:
- ./tests/Feature/resources/wiremock:/home/wiremock/mappings
我们添加了 wiremock 服务,并将其映射到本地默认端口 8080。
我们还在 Wiremock 容器查找存根的位置提供了一个卷。
Wiremock 支持 API 模拟,即对符合特定条件的请求返回预制 HTTP 响应。
通过 .json 文件为响应创建存根意味着我们可以默认提供一组响应。
我们还可以在测试过程中通过 REST API 以编程方式添加存根,
这对于测试边缘情况和尚未发布的新功能非常有用。
让我们通过启动容器来验证 Wiremock 是否已准备就绪:
docker compose up -d
我更喜欢使用 -d 在后台运行 Docker 服务:
docker compose ps
NAME COMMAND SERVICE STATUS PORTS
wiremock-demo-wiremock-1 "/docker-entrypoint.…" wiremock running 0.0.0.0:8080->8080/tcp
您也可以在浏览器中访问,应该会看到与下面类似的响应:
http://localhost:8080/__admin/
/__admin 端点是我们与 Wiremock REST API 交互的基本 URL。
现在,它还不是很有用,但我们很快就会重新访问它,届时它会更有用!
我们的第一个 Wiremock 存根
我们准备创建第一个 Wiremock 存根,用于演示存根如何工作。
我们将在此基础上测试代码中的边缘情况,如失败或 404 Not Found 响应。
我不喜欢 "hello world "教程,但我认为在开始使用 Wiremock 时会发生很多事情,
而提供一个简单的第一个示例有助于理解存根工作原理的基本原理。
让我们在 fixtures 文件夹中创建 hello-world.json 文件,从而创建第一个存根:
touch tests/Feature/resources/wiremock/hello-world.json
在 hello-world.json 文件中添加以下内容:
{
"request": {
"method": "GET",
"url": "/hello-api/v1/hello-world"
},
"response": {
"status": 200,
"jsonBody": {
"message": "Hello, World!"
},
"headers": {
"Content-Type": "application/json"
}
}
}
我们的 Wiremock 存根由请求键和响应键组成,描述了我们应该如何模拟请求。
请注意 /hello-api/v1 前缀,这通常是我组织 URL 路径的方式。
我们不希望为每个应用程序接口都建立一个单独的 Wiremock 实例,
因此可以通过提供一个前缀,遵循与应用程序接口网关类似的模式。
在我们的示例中,hello-world 部分是代码中的实际 API 资源。
如果我们在此时停止 Docker 容器,
就能在 Wiremock 的管理 API 中看到我们的存根请求/响应映射:
docker compose down
docker compose up -d
现在,当您访问 http://localhost:8080/__admin/ 时,应该会看到与下面类似的响应:
我们还可以使用 HTTP 客户端(如 Postman)、浏览器或类似 cURL 的软件来验证响应:
$ curl -i http://localhost:8080/hello-api/v1/hello-world
HTTP/1.1 200 OK
Content-Type: application/json
Matched-Stub-Id: 212b63c1-13cb-4648-9a6f-db4569ead7e4
Vary: Accept-Encoding, User-Agent
Transfer-Encoding: chunked
{"message":"Hello, World!"}
我们的存根非常简单,只需将请求 URL 匹配到 /hello-api/v1/hello-world。
但是,如果我们提出一个带有查询参数的请求,你就会发现请求失败了:
Wiremock 提供了与我们请求的内容最接近的匹配提示,这在调试存根问题时非常有用。
我们可以使用的另一个有用的端点是 __admin/requests 端点:
请注意 wasMatched 属性,它表明根据存根的匹配条件,我的最新请求没有成功。
Wiremock 中的请求匹配功能非常强大,我鼓励大家尝试使用以了解更多。
https://wiremock.org/docs/request-matching/
为了激发你的好奇心,我们可以制作另一个包含请求匹配的存根 hello-paul.json:
{
"request": {
"method": "GET",
"urlPath": "/hello-api/v1/hello-world",
"queryParameters": {
"name": {
"matches": "Paul"
}
}
},
"response": {
"status": 200,
"jsonBody": {
"message": "Hello, Paul!"
},
"headers": {
"Content-Type": "application/json"
}
}
}
注意从 url 到 urlPath 的变化以及新的 queryParameters 属性。
我们还可以在这里使用正则表达式:
"queryParameters": {
"name": {
"matches": "^[A-Za-z]+$"
}
}
创建示例 API 客户端
现在我们对 Wiremock 和运行中的模拟 HTTP 服务器有了基本的了解。
现在是时候演示如何将其与 Laravel 相连接了。
我们将扩展更多关于测试和 Wiremock 的高级主题,
但现在,让我们用一个示例来结束这第一遍。
如果你正在跟读,请创建一个新类,以便我们进行演示:
touch app/HelloWorldApi.php
然后像这样创建类:
namespace App;
use GuzzleHttp\Client as GuzzleClient;
class HelloWorldApi
{
public function __construct(
protected GuzzleClient $client
) {}
/**
* @return array{message: string}
*/
public function sayHello()
{
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $this->client->get('hello-world');
return json_decode((string) $response->getBody(), true);
}
}
我们的示例服务使用 Guzzle 客户端实例。
第三方代码的形式会有所不同,但希望你能为你的客户端配置基础 URI。
请注意我们是如何以相对方式调用 hello-world,依靠客户端的 base_uri 配置来创建正确的 URL。
接下来,让我们在 AppServiceProvider.php 文件中注册服务,以便试用这个演示:
use App\HelloWorldApi;
use GuzzleHttp\Client as GuzzleClient;
// ...
$this->app->bind(HelloWorldApi::class, function ($app) {
/** @var \Illuminate\Config\Repository $config */
$config = $app['config'];
$client = new GuzzleClient([
'base_uri' => $config->get('services.hello_world.base_uri'),
]);
return new HelloWorldApi($client);
});
最后,让我们使用 config/services.php 配置服务,
就像在上述服务提供者中使用的那样:
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
// ...
'hello_world' => [
'base_uri' => env('HELLO_WORLD_API_BASE_URI', 'https://hello-world.example.com/v1/'),
],
];
我们的服务会配置 Guzzle 的 base_uri 选项,
如 Quickstart: Creating a Client文档中的定义。
https://docs.guzzlephp.org/en/stable/quickstart.html%23creating-a-client
在我看来,我们配置的是尾部斜线,这是最便携的定义 base_uri 选项的方式。
# Internal staging example
http://my-internal-service.vpc
# Staging
http://stg-api.mycompany.com/my-service/v1/
在测试中使用 Wiremock
我们已经准备好使用 Wiremock 来模拟 HTTP 响应,让客户端体验一下。
困难的部分已经解决,让我们创建 Pest 测试来演示它是如何工作的:
touch tests/Feature/HelloWorldApiTest.php
内容如下
use App\HelloWorldApi;
it('says hello', function () {
$result = app(HelloWorldApi::class)->sayHello();
expect($result)->toBe(['message' => 'Hello, World!']);
});
接下来,我们需要通过项目的 phpunit.xml 文件配置 Wiremock 服务器。
这是重写基础 URI 的关键,这样我们的测试就能指向 wiremock,而不是真正的服务:
<php>
<env name="HELLO_WORLD_API_BASE_URI" value="http://localhost:8080/hello-api/v1/"/>
</php>
注意前面提到的尾部斜线 (/),以确保我们的 API 客户端调用在各种 base_uri 配置下均可移植。
现在,我们应该可以运行测试并验证它是否通过了:
vendor/bin/pest tests/Feature/HelloWorldApiTest.php
现在你应该看到测试通过了!
如果遇到任何错误或失败,请确保 hello-world.json 文件正确无误,
或许可以关闭并重启 Wiremock 容器,以确保它拥有最新版本:
docker compose down
docker compose up -d
值得注意的是,当你处理 JSON 存根时,它们会被挂载为 Docker 卷。
不过,在移除容器并创建新容器之前,您的更改不会反映在 Wiremock 服务器中。
如果重新运行测试,显示测试请求的历史记录
http://localhost:8080/__admin/requests
当测试失败时,请求历史记录对调试的价值不言而喻,你可以立即查看请求以及是否无法匹配。
总结
本教程涉及的内容很多,但我希望你能看到 Wiremock 在测试 API 方面的潜力。
Wiremock 还能让你针对尚未上线的 API 功能编写代码。
您可能正在与另一个团队合作构建您正在使用的第一方 API,并希望针对真正的 HTTP 服务测试集成。
使用模拟 HTTP 服务器会给测试生命周期带来一定的复杂性,这一点我不会讳言。
对于简单的集成来说,这是矫枉过正。
我们现在需要在测试套件中运行 Docker,但我认为,
在集成多个服务和测试代码中的复杂边缘情况时,增加复杂性是值得的。
使用中间件或 Mockery mock 来避免在代码中进行 mock 也很不错。
更多使用场景, 比如:
深入探讨在测试过程中编写自定义 HTTP 存根、
针对模拟服务器的历史记录进行断言,以及在测试运行后清理自定义存根。
我建议你查看 WireMock 用户文档,
https://wiremock.org/docs/
了解更多关于创建 HTTP 存根和为测试运行模拟 HTTP 服务器的信息!
122 在
学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..123 在
Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..原梓番博客 在
在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..博主 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..1111 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
Copyright·© 2019 侯体宗版权所有·
粤ICP备20027696号