Swagger定義から生成したAPIのテストをCIで実行する
この記事は スターフェスティバル スターフェスティバル Advent Calendar 2022の14日目の記事です。
スターフェスティバル株式会社 ソフトウェアエンジニアのishidaです。
先日チーム内で「Clean Craftsmanship 規律、基準、倫理」という書籍のサマリー共有会があり、テストの重要性の認識が深まっている今日この頃です。
APIのテストをCIで回せたらいいなって思ったので、Swagger定義からテストを生成してGitHub Actionsで実行してみようと思います。
方針
弊社ではOpenAPI(Swagger)を用いて開発をしていますので、 前述の通りSwagger定義からテストを生成してCIで実行したい気持ちがあります。
そのため
- Swagger(OpenAPI3)に対応していること
- CIで実行しやすい(手軽にコマンドラインで実行できる)
の条件のpackageの利用が望ましいです。
条件に合致しそうなツールを調べたところ
等がありました。
1はAPI定義からCollectionを作成してNewmanでテスト〜とstepが多く感じ、2はOpenAPI3がexperimentalでした。
3のStep CIはOpenAPI3に対応していて手軽に導入できそうだったため触ってみることにしました。
Step CIについて
コマンドラインでYAMLで設定したテストを実行できるオープンソースのライブラリです
Step CIは REST, GraphQL, gRPC, tRPC, SOAPなどのAPIのテストに対応しています。
他にもjestなどのテスティングフレームワークとの連携もできるようです。
実践してみる
1.NestJSの準備
せっかくなのでSwagger定義の作成も自動化したいのでNestJSでのinitializeから始めます
$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start
@nestjs/swaggerのインストール
$ npm install --save @nestjs/swagger
@nestjs/swagger は @ApiProperty などのDecoratorを記載することによってSwagger定義を作成できるライブラリです
main.tsにapiのprefixを追記します
app.setGlobalPrefix('api/v1');
Swaggerのjsonを生成するファイルを作成します。
@nestjs/swaggerのデコレータやDocumentBuilderで設定したconfigの内容からjsonを生成しています。
詳しくは下記を参照してください
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import * as fs from 'fs';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('example')
.setVersion('1.0')
.addServer('http://localhost:3000/api/v1')
.addTag('test')
.build();
const document = SwaggerModule.createDocument(app, config);
fs.writeFileSync('./openapi.json', JSON.stringify(document));
}
bootstrap();
nest-cli.jsonのpluginsに"@nestjs/swaggerを追記します。
@ApiPropertyなどのDecoratorの記載が省略できたり、コメントに記載した内容がSwagger定義に反映されるようになります。
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
app.controllerにタグを追加します。(タグを設定しない場合、Step CIでテスト用のyml生成する際にエラーが発生しました)
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('stafes')
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
packge.jsonにscript追加して準備完了です。
npm run swagger:export
を実行することでgenerate-open-api-json.ts
からswagger定義ファイルが生成されます。
"swagger:export": "nest start --entryFile generate-open-api-json",
2.Step CIでテストをしてみる
Swagger定義ファイルの準備ができたのでStep CIでテストをします。
stepciのinstall
$ npm install stepci
packge.jsonにscript追加
"generate:stepci-workflow": "stepci generate",
"test:stepci": "npm run swagger:export && npm run generate:stepci-workflow && stepci run workflow.yml"
test:stepci
を実行してみます。
stepci generate
を実行してSwagger定義からテストのワークフローを生成し、stepci run workflow.yml
でテストをしています。
デフォルトでworkflow.ymlが生成されますが、オプションで設定可能です。
$ npm run test:stepci
PASS stafes(設定したタグ)
Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 1 passed, 1 total
Time: 0.101s, estimated 0s
Workflow passed after 0.101s
Give us your feedback on https://step.ci/feedback
テストが通りました。
workflow.ymlを見てみると、swagger定義からテストが生成されていることがわかります。
/ に対してリクエストをするとステータス200でstringのレスポンスが返ってくるかのテストが生成されていますね。
version: "1.0"
name: example
config:
http:
baseURL: http://localhost:3000/api/v1
tests:
stafes:
name: ""
steps:
- id: AppController_getHello
name: ""
http:
url: /
method: GET
check:
status: 200
schema:
type: string
components:
schemas: {}
今回は説明を割愛しますが、他の項目もテストができそうでした。
Swagger定義をより詳細に記載することで、詳細なテストができると嬉しいですね。
3.CIで実行
次はGitHub Actionsの設定をします
Dockerfileとdocker-compose.ymlを作成し、PullRequest作成時にActionが実行されるように設定します。
FROM node:18.12.1-buster as builder
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
COPY . /home/node/app
RUN npm ci && npm run build
FROM node:18.12.1-buster-slim as run
# Set time zone to Asia/Tokyo
ENV TZ="Asia/Tokyo"
RUN ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime && echo "$TZ" > /etc/timezone
COPY / /
WORKDIR /home/node/app
EXPOSE 3000
CMD [ "npm", "run", "start" ]
version: "3"
services:
api:
build:
context: .
dockerfile: ./Dockerfile
container_name: step-ci-with-nest-js
tty: true
stdin_open: true
volumes:
- .:/step-ci-with-nest-js
- /step-ci-with-nest-js/node_modules
ports:
- '3000:3000'
command: "npm run start"
GitHub Actionsのymlを作成
name: CI/Tests
on:
pull_request:
types:
- opened
- synchronize
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- id: nodenv
uses: nodenv/actions/node-version@main
- uses: actions/setup-node@v3
with:
node-version: "${{ steps.nodenv.outputs.node-version }}"
- name: Build
run: npm ci
- name: Install dependencies
run: npm run build
- name: docker compose up
run: docker compose -f docker-compose.yml up -d
- name: Test
run: docker compose -f docker-compose.yml exec api npm run test:stepci
commitしてPullRequestを作成し、テストが実行されていることが確認できました。
まとめ
今回はNestJS、Step CI、GitHub Actionsを使ってAPIのテストをCIで実行してみました。
CIでAPIのテストをするのは初めてでしたが、Step CIはSwagger定義があればコマンドを実行するだけなので導入も簡単でした。
今後ももう少し触ってみて、より詳細なテストができないか試してみようと思います。
採用について
弊社では一緒に開発する仲間を絶賛募集中です!
ウェルウェルカムカムです!
Discussion