会社で「SwaggerHubを導入するから」と担当を任され、
既存のAPIにOpenAPIを導入する方法を検討中のtenryuです。
多くのOpenAPI導入記事で作成した仕様書とAPIに相違が発生しないようテストが実施されており、
弊社のSwaggerHub導入でも必須だと感じました。
私の担当プロジェクトでは、APIの実装にPHPフレームワークのLaravelが使用されています。
「Laravelで実装したAPI」と「仕様書」に相違が無いことを確認するため,
openapi-psr7-validatorというライブラリを利用しテストしている記事がいくつかありました。
しかし、わかりにくい部分が多々あったり、そもそも情報量が少なかったのでまとめたいと思います。
openapi-psr7-validator
How To Validateでいきなり躓く
使い方がよくわからない・・・
ServerRequest Messageのバリデーション?
いろいろ検索して出てくるのは、PSR-7に変換してからではないと使えないということ
LaravelのリクエストとレスポンスはPSR-7には準拠していないということ?
そもそもPSR-7って何?
PSR(PHP Standards Recommendations)と言われるPHPコーディング規約の一つで、HTTPのリクエスト、レスポンスに関する規約らしい
LaravelとPSR-7
PSR-7標準は、リクエストとレスポンスを含むHTTPメッセージのインターフェイスを規定しています。Laravelリクエストの代わりにPSR-7リクエストのインスタンスを取得したい場合は、最初にいくつかのライブラリをインストールする必要があります。LaravelはSymfony HTTP Message Bridgeコンポーネントを使用して、通常使用するLaravelのリクエストとレスポンスをPSR-7互換の実装に変換します。
Laravelの日本語マニュアルを確認すると、Laravelのリクエスト、レスポンスはPSR-7準拠ではないらしい
PSR-7互換にするにはライブラリをインストールする必要があるとのこと
ライブラリ使用方法の参考
一番上のQiitaの記事は古く流用できる部分が少ないが、使い方のイメージは沸いた。
特に参考になったのが、PHP Conference Japan 2021で登壇されていた株式会社ゆめみの皆川さんの発表とリポジトリ。
YouTubeで発表を聞き、リポジトリを確認したらかなり理解できた。
実際に使ってみる
ユーザー情報を取得する簡単なAPIを実装しテストしてみる
環境構築
Laravelの環境構築にはいつもお世話になってるucanさんの記事を使わせていただいた
マルチステージビルドに対応するなど、以前よりもさらに使いやすくなっていた
テストコードをデバッガで確認しようと思い、Xdebugをインストールした
ただ、テストコードはXdebugを使っても止まらず・・・・
テストコードってデバッグできないの?
テストコード程度、皆さんはデバッグしないのだろうか・・・
インストール方法はリポジトリのWikiで確認できる。
APIの実装
Resourceなどは使わず、ただ単にユーザーのidとnameを返すだけ。
<?php
use Illuminate\Support\Facades\Route;
Route::get('/user', function() {
return response()->json([
'id' => '1',
'name' => 'Taro',
], 200);
});
Open Api Specの作成
仕様書はSwaggerHubを使用し作成。
openapi: 3.0.0
servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/INDEX0205_1/laravel-test/1.0.0
- description: Laravel OpenAPI Test
url: aa
info:
description: 「Laravel」と「OpenAPIの仕様書」間のテスト
version: "1.0.0"
title: Laravel OpenAPI Test
tags:
- name: users
description: ユーザー取得
paths:
/api/user:
get:
tags:
- users
summary: searches user
responses:
'200':
description: search results matching criteria
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: string
ライブラリを使用したテスト
<?php
declare(strict_types=1);
namespace Tests\Feature;
use GuzzleHttp\Psr7\Response;
use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use Tests\TestCase;
class SampleControllerTest extends TestCase {
public function test_assertion2(): void
{
$uri = '/api/user';
// アサーション
$response = $this->get($uri);
$response
->assertOk()
->assertExactJson([
'id' => '1',
'name' => 'Taro',
// バリデータの生成
$validator = (new ValidatorBuilder)->fromYamlFile(__DIR__. '/../ApiSpec/laravel-test-1.0.0-swagger.yaml')->getResponseValidator();
// APIを実行しレスポンスを取得
$response = $this->json(
'GET',
$uri,
[],
[]
);
// PSR-7準拠のレスポンスへ変換
$psr7Response = new Response(
$response->getStatusCode(),
$response->headers->all(),
$response->getContent(),
);
// 「仕様書のどの項目をテストするか」を変数に格納
$address = new \League\OpenAPIValidation\PSR7\OperationAddress($uri, 'get');
$validator->validate($address, $psr7Response);
}
}
手順としては以下
- バリデータを生成
- APIを実行しレスポンスを取得。PSR-7準拠のレスポンスへ変換
- 今回はGuzzleを使用。他にも方法はある
- 仕様書中のどの項目をテストするか、の情報を変数に格納
- バリデーション実行
※テスト内でassertOk()などのアサーションを実行しない場合、以下のように出力されるため注意
This test did not perform any assertions
C:\src\tests\Feature\SampleControllerTest.php:13
OK, but incomplete, skipped, or risky tests!
Tests: 2, Assertions: 1, Risky: 1.
実行してみる
問題なくテストが通った
$ ./vendor/bin/phpunit
PHPUnit 9.5.24 #StandWithUkraine
.. 2 / 2 (100%)
Time: 00:00.834, Memory: 22.00 MB
OK (2 tests, 3 assertions)
わざとテストを失敗してみる
nameの型をintegerへ変更してみる
openapi: 3.0.0
servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/INDEX0205_1/laravel-test/1.0.0
- description: Laravel OpenAPI Test
url: aa
info:
description: 「Laravel」と「OpenAPIの仕様書」間のテスト
version: "1.0.0"
title: Laravel OpenAPI Test
tags:
- name: users
description: ユーザー取得
paths:
/api/user:
get:
tags:
- users
summary: searches user
responses:
'200':
description: search results matching criteria
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: integer //変更
エラーが出力されたのでOK
$ ./vendor/bin/phpunit
PHPUnit 9.5.24 #StandWithUkraine
.E 2 / 2 (100%)
Time: 00:00.318, Memory: 22.00 MB
There was 1 error:
1) Tests\Feature\SampleControllerTest::test_assertion2
League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody: Body does not match schema for content-type "application/json" for Response [get /api/user 200]
まとめ
openapi-psr7-validatorを使用し、「Laravelで実装したAPI」と「OpenAPIの定義ファイル」のテストを実行してみました。
ライブラリの使い方がREADMEを読んだだけではわかりにくい雑魚なので、参考記事を書いてくださる方には頭があがりません。
今後はSwaggerHubで仕様書を更新しプッシュ、Github Actionsでテストを自動実行、という流れを構築していきたいと思います。