はじめに
グロースエクスパートナーズ Advent Calendar 2020 3日目。
yitoです。
API開発をしている中で、テストするのに毎回クライアントツールにリクエストを用意してAPIを呼び出すのが手間でした。
それを、コマンド一発で、API仕様書に定義した全てのAPIをテストできるようにしたので、その方法を紹介します。
概要
OpenAPIドキュメントをAPIクライアントツールで利用可能なJSONに変換し、それをCLIライブラリで実行します。
これを実現する、以下について順番に説明していきます。
- Postmanについて
- OpenAPI 3.0 to Postman Collection v2.1.0 Converter
- Postman Collection SDK
- newman
Postmanについて
APIリクエストのクライアントツールにPostmanがあります。
Postmanは、以下のようなことができます。
- 呼び出したいAPIのリクエスト情報を書き出して保存
- => Postman Collections
- APIのテストスクリプト実装
- ステータスコード、レスポンスボディのプロパティのテストなどが可能
- https://learning.postman.com/docs/writing-scripts/test-scripts/
書き出したリクエスト情報を保存すると、画面左側にあるPostman Collectionsに追加され、フォルダでの管理ができます。
このPostman Collectionsは、Webに公開する(制限をかけることも可能?)か、JSONでのexport/importで、開発チーム内で共有が可能です。
OpenAPI 3.0 to Postman Collection v2.1.0 Converter
OpenAPI 3.0 to Postman Collection v2.1.0 Converterは、OpenAPIドキュメントをPostman CollectionのJSONに変換する、NodeJSで実行可能なライブラリです。
使い方は簡単で、OpenAPIのyamlを読み込んで、このライブラリのクラスに渡して実行するだけです。
const fs = require('fs');
const readFileData = fs.readFileSync('OpenAPIドキュメントのファイルパス', {encoding: 'UTF8'});
const Converter = require('openapi-to-postmanv2');
Converter.convert({type: 'string', data: readFileData}, {},
(err, conversionResult) => {
if (!conversionResult.result) {
console.error('API定義をPostman Collectionに変換できませんでした',
conversionResult.reason);
return;
}
const convertData = conversionResult.output[0].data;
// Postman CollectionsのJSONファイルを生成する
fs.writeFile('postman-collections.json', JSON.stringify(convertData, null, 4), (err) => {
if(err) console.error(err)
});
}
);
この生成されたJSONファイルを読み込むと、Postmanにて利用ができます。
ただ、このままだと、各APIのリクエストパラメータが <long>
<string>
のようになっていて、このままだと呼び出したいAPIのパラメータとしては不適切なので、使う際は書き換えが必要です。
手で1つ1つ直すのは面倒なので、Postman Collection SDKを使います
Postman Collection SDK
Postman Collection SDKは、Postman Collectionの生成・更新のための、NodeJSで実行可能なSDKです。
OpenAPI 3.0 to Postman Collection v2.1.0 Converterにて生成したPostman CollectionのJSONを、このSDKを使って編集します。
Postman CollectionのJSONファイルを読み込み、SDKにてCollectionをnewすると、以下のようなクラスが生成されます。
※クラスについての詳細
- Collection
- Postman Collectionの1つに相当
- メンバーにItemGroupを持つ
- ItemGroup
- Postman Collectionの1フォルダに相当
- メンバーにItemを持つ
- Item
- Postman Collectionの1つのAPIに相当
- メンバーにRequestを持つ
- Request
- APIのURL、リクエストパラメータに関する値
- メンバーにHeaderList, EventList, VariableListなどを持つ
<long>
<string>
のような値を実際に使いたい値に変えるため、 Request
の値を書き換えます。
こちらにあるOpenAPIドキュメント(api-spec.yml)、Postman Collection SDKを使った実装(postman-test/index.js)を使いながら説明します。
先ほどの実装に以下を追加します。
...
const convertData = conversionResult.output[0].data;
const collection = new Collection(convertData);
collection.items.all().forEach(item => updateItem(item))
// Postman CollectionsのJSONファイルを生成する
fs.writeFile('postman-collections.json', JSON.stringify(collection, null, 4), (err) => {
if(err) console.error(err)
});
...
collection.items
に ItemGroup
または Item
があります。そこから、 Request
を取り出して、 <long>
<string>
のような値を、使いたい値に書き換えます。
...
const { v4: uuidv4 } = require('uuid');
const updateRequestVariable = (request, key, value) => {
const findVariable = request.url.variables.find(
variable => variable.key === key);
if (findVariable) {
findVariable.update({
key: key,
value: value
})
}
}
const updateRequestQuery = (request, key, value) => {
const findQueryParam = request.url.query.find(
queryParam => queryParam.key === key);
if (findQueryParam) {
findQueryParam.update({
key: key,
value: value
})
}
}
const updateRequest = (request) => {
// Authorization
if (request.auth && request.auth.type === 'apikey') {
request.auth.update({
"key": "api_key",
"value": `api-key-${uuidv4()}`
}, 'apikey')
}
// 書籍関連のAPI
if (request.url.getPath().includes('books')) {
updateRequestVariable(request, 'bookId', '100000001');
}
// ユーザー関連のAPI
if (request.url.getPath().includes('users')) {
updateRequestVariable(request, 'username', 'user-XXXX');
updateRequestQuery(request, 'username', 'user-XXXX');
updateRequestQuery(request, 'password', 'pass-XXXX');
}
return request;
}
const updateItem = (item) => {
if (item.items) {
item.items.all().forEach(item => updateItem(item));
} else {
item.request = updateRequest(item.request);
}
return item;
}
...
レスポンスに対するテストも実行したいので、 updateItem
メソッドに以下のコードを追加します。
...
const updateItem = (item) => {
if (item.items) {
item.items.all().forEach(item => updateItem(item));
} else {
// 追加
item.events.add({
listen: 'test',
script: {
exec: "pm.test('response 200 test', () => {\n"
+ " pm.response.to.have.status(200);\n"
+ "});"
},
type: 'text/javascript'
})
...
これでリクエストパラメータの書き換えがされた状態で、Postman CollectionのJSONが生成されます。
毎回JSONをimportしてAPIを呼び出すのは面倒なので、CLIだけで実行できるように、newmanを使います.
newman
newmanは、CLIでPostman CollectionのJSONを読み込みAPIをリクエストする、NodeJSで実行可能なライブラリです。
先ほどのコードで生成したJSONを、newmanで実行するように変えます。
...
const collection = new Collection(convertData);
collection.items.all().forEach(item => updateItem(item))
fs.writeFile('postman-collections.json', JSON.stringify(collection, null, 4), (err) => {
if(err) console.error(err)
});
newman.run({
collection: collection,
reporters: 'cli',
environment: require('./local.postman_environment.json')
}, (err) => {
if (err) console.error(err);
});
...
-
newman.run()
- collection: Postman CollectionのJSONを指定
- reports: 実行結果の出力方法の指定。CLI、Junit、JSON等が可能
- envioroment: Postmanには環境ごとの変数の指定があり、生成されたJSONには
baseUrl
の指定が必要なため、向き先を指定したJSONを用意して読み込む
実行すると以下のようになります。
% npm run start
> node index.js
newman
Book Management
❏ books
↳ 書籍更新API
PUT http://localhost:8080/v1/books [200 OK, 123B, 235ms]
✓ response 200 test
↳ 書籍登録API
POST http://localhost:8080/v1/books [200 OK, 123B, 19ms]
✓ response 200 test
↳ 書籍一覧取得API
GET http://localhost:8080/v1/books [200 OK, 800B, 11ms]
✓ response 200 test
↳ タグ絞り込み検索API
GET http://localhost:8080/v1/books/findByTags?tags=<string>&tags=<string> [200 OK, 800B, 14ms]
✓ response 200 test
❏ books / {book Id}
↳ 書籍詳細取得API
GET http://localhost:8080/v1/books/100000001 [200 OK, 290B, 10ms]
✓ response 200 test
↳ 書籍削除API
DELETE http://localhost:8080/v1/books/100000001 [200 OK, 123B, 7ms]
✓ response 200 test
❏ users
↳ ユーザー登録API
POST http://localhost:8080/v1/users [200 OK, 123B, 8ms]
✓ response 200 test
↳ ユーザー一覧取得API
GET http://localhost:8080/v1/users [200 OK, 123B, 6ms]
✓ response 200 test
↳ ログインAPI
GET http://localhost:8080/v1/users/login?username=user-XXXX&password=pass-XXXX [200 OK, 123B, 8ms]
✓ response 200 test
↳ ログアウトAPI
GET http://localhost:8080/v1/users/logout [200 OK, 123B, 6ms]
✓ response 200 test
❏ users / {username}
↳ ユーザー詳細取得APi
GET http://localhost:8080/v1/users/user-XXXX [200 OK, 123B, 6ms]
✓ response 200 test
↳ ユーザー更新API
PUT http://localhost:8080/v1/users/user-XXXX [200 OK, 123B, 5ms]
✓ response 200 test
↳ ユーザー削除API
DELETE http://localhost:8080/v1/users/user-XXXX [200 OK, 123B, 7ms]
✓ response 200 test
→ タグ一覧取得API
GET http://localhost:8080/v1/tags [200 OK, 123B, 6ms]
✓ response 200 test
┌─────────────────────────┬───────────────────┬──────────────────┐
│ │ executed │ failed │
├─────────────────────────┼───────────────────┼──────────────────┤
│ iterations │ 1 │ 0 │
├─────────────────────────┼───────────────────┼──────────────────┤
│ requests │ 14 │ 0 │
├─────────────────────────┼───────────────────┼──────────────────┤
│ test-scripts │ 14 │ 0 │
├─────────────────────────┼───────────────────┼──────────────────┤
│ prerequest-scripts │ 0 │ 0 │
├─────────────────────────┼───────────────────┼──────────────────┤
│ assertions │ 14 │ 0 │
├─────────────────────────┴───────────────────┴──────────────────┤
│ total run duration: 654ms │
├────────────────────────────────────────────────────────────────┤
│ total data received: 1.37KB (approx) │
├────────────────────────────────────────────────────────────────┤
│ average response time: 24ms [min: 5ms, max: 235ms, s.d.: 58ms] │
└────────────────────────────────────────────────────────────────┘
以上で、コマンド一発でAPI全てをテストできるようになりました。
参考
- https://learning.postman.com/docs/writing-scripts/test-scripts/
- https://www.postman.com/collection/
- https://github.com/postmanlabs/openapi-to-postman
- https://www.postmanlabs.com/postman-collection/index.html
サンプルコード