[PHPUnit 测试] 如何使用 Fixtures 数据来处理第三方接口响应?
我被问到很多关于 API 响应测试的问题,你应该怎么做,又从哪里开始。当测试 APIs 时,我有一个通用的规则,那就是:「测试你的代码,并只测试你的代码」。这是什么意思呢? 让我解释一下:
在你的应用程序中,你正在编写与 API 集成的代码(此 API 将不受您的控制),并且你将很可能使用一些软件包库来集成此 API。因此,不要花时间去模仿和复制你无法控制的库或服务的行为,它会花费你更多的时间。相反, 把测试的重点放在你能掌控的东西上; 如何发送请求以及如何响应 API 响应。除此之外,你不能做的更多。
我之前写过关于如何在 Laravel 中集成第三方 APIs 的博客推文,所以我不打算详细描述如果集成,并只关注测试方面。想象一下我们有三个需要去集成的端点:
- 一个
GET
端点 - 一个
POST
端点 - 一个
DELET
端点
这是一种典型的 CRUD 风格的行为,你想在哪里 读取
什么数据,创建
什么数据并 删除
什么数据。为此,我将使用一个虚构的 API,因为重要的是方法,而不是 API 的细节。
我们的 API
我们的 API 是一个简单的 API,让允许我们管理我们可以访问的图书馆,稀松平常的。我们可以在这个 API 中添加新书,当我们把书送给别人或以某种方式扔掉时,我们能删除它们。因此,让我们从第一个想法开始,获取我们图书馆中的书籍清单。为了简单起见,我不打算在这些示例中处理身份验证。我将使用 Laravel 的 Http facade 例子使这一点更加简单明了。
$response = Http::get('https://books-api.com/books');
完美,我们已经发出了一个请求并接收了一个响应,现在我们可以相信这个 Http facade,它已经被 Laravel 团队进行了很好的测试,因此,我们不需要去测试请求是否真的发送到了这里。我们需要做的是测试响应内容是否符合我们所预期的。在这里,使用 PestPHP 这样的库确实派上用场,因为您用于测试的语言非常容易于理解。
it('can get a list of books from the API', function () {
$response = Http::get('https://books-api.com/books');
expect($response->json())->toEqual('??? what goes here ???');
});
正如你从上面的实例中看到的,我们正在发送一个请求,并测试 Json 响应是否与我们可以处理的内容相同。这部分我们如何来做? 为此,我们需要打开 tests/Pest.php
并添加一个自定义的函数,让我们可以发挥一些魔力。这将允许我们加载一个 Fixture
并将它传递给 Http Facade 中以获得响应。
function fixture(string $name): array
{
$file = file_get_contents(
filename: base_path("tests/Fixtures/$name.json"),
);
if(! $file) {
throw new InvalidArgumentException(
message: "Cannot find fixture: [$name] at tests/Fixtures/$name.json",
);
}
return json_decode(
json: $file,
associative: true,
);
}
如你所见,我通常会在 tests 目录中创建一个名为 Fixtures
的目录,以便我能存储来自 API 的示例响应,以进行测试。
让我再一次演练测试代码,但是这一次我们将伪造请求并测试响应。
it('can get a list of books from the API', function () {
$responseData = fixture('BooksApi/book-list');
Http::fake([
'*' => Http::response(
body: $responseData,
status: 200,
),
]);
$response = Http::get('https://books-api.com/books');
expect($response->json())->toEqual($responseData);
});
所以我们要做的是从 fixture 中拿到 json 数据,将其传递给Http::faker()
方法,以便任何请求将返回这个响应,然后预期当我们发送请求时,我们的输出就是我们所预期的。所以 Fixture 数据本身通常是从 API 文档中获取的,可能看起来有点像:
// tests/Fixtures/BooksApi/book-list.json
{
"data": {
[
{
"id": "12345",
"title": "The Lord of The Rings",
"author": "J R R Tolkien"
},
{
"id": "12346",
"title": "The Hobbit",
"author": "J R R Tolkien"
}
]
}
}
因此,我们可以将以上的测试扩展更多。
it('can get a list of books from the API', function () {
$responseData = fixture('BooksApi/book-list');
Http::fake([
'*' => Http::response(
body: $responseData,
status: 200,
),
]);
$books = Http::get('https://books-api.com/books');
expect($books->json())->toEqual($responseData);
$books->json('data')->each(function ($book) {
expect($book['author'])->toEqual('J R R Tolkien');
});
});
因此,我们现在正在映射请求,并确保其格式符合我们的预期。
post 请求我们也可以这样做,我们可以创建一些虚假数据,并在伪造时使用Http facade发布,并测试我们的操作结果。
it('can create a new book', function () {
$responseData = fixture('BooksApi/create-book');
Http::fake([
'*' => Http::response(
body: $responseData,
status: 201,
),
]);
$book = Http::post('https://books-api.com/books', [
'title' => 'Spock Must Die!',
'author' => 'James Blish',
]);
expect($books->json())->toEqual($responseData);
expect($book->json('data'))->title->toEqual('Spok Must Die!');
});
我们现在正在测试响应是否匹配,以及当我们对数据进行交互时,属性是否与我们预期的匹配。
同样的事情也可以使用 DELETE
端点来完成,在该端点中,我们向端点发送请求并期望以特定方式格式化响应。
it('can delete a book from the API', function () {
Http::fake([
'*' => Http:response(
data: null,
status: 204,
),
]);
$response = Http::delete('https://books-api.com/books/12345');
expect($response->status())->toEqual(204);
});
在上面的例子我们的接口没有返回数据,因为我们刚刚删除了资源,所以我们所需要做的就是检查状态码是否匹配。除此之外,对于我们的应用程序来说,知道任何事情都不重要,我们关心的是我们请求了一个操作,并且我们从API得到了预期的答复,确认我们的操作已完成或至少已监听。
API测试并不困难,您可以对其进行深入的了解,但是您只需要深入到应用程序所关心的范围。超出这个范围将浪费您用于代码其他领域的宝贵时间。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。