📱

Swagger定義から生成したAPIのテストをCIで実行する

2022/12/14に公開

この記事は スターフェスティバル スターフェスティバル Advent Calendar 2022の14日目の記事です。

スターフェスティバル株式会社 ソフトウェアエンジニアのishidaです。
先日チーム内で「Clean Craftsmanship 規律、基準、倫理」という書籍のサマリー共有会があり、テストの重要性の認識が深まっている今日この頃です。
APIのテストをCIで回せたらいいなって思ったので、Swagger定義からテストを生成してGitHub Actionsで実行してみようと思います。

方針

弊社ではOpenAPI(Swagger)を用いて開発をしていますので、 前述の通りSwagger定義からテストを生成してCIで実行したい気持ちがあります。
そのため

  • Swagger(OpenAPI3)に対応していること
  • CIで実行しやすい(手軽にコマンドラインで実行できる)

の条件のpackageの利用が望ましいです。

条件に合致しそうなツールを調べたところ

  1. PostmanNewman を使う方法
  2. Dredd を使う方法
  3. Step CI を使う方法

等がありました。
1はAPI定義からCollectionを作成してNewmanでテスト〜とstepが多く感じ、2はOpenAPI3がexperimentalでした。
3のStep CIはOpenAPI3に対応していて手軽に導入できそうだったため触ってみることにしました。

Step CIについて

コマンドラインでYAMLで設定したテストを実行できるオープンソースのライブラリです
Step CIは REST, GraphQL, gRPC, tRPC, SOAPなどのAPIのテストに対応しています。
他にもjestなどのテスティングフレームワークとの連携もできるようです。
https://stepci.com/

実践してみる

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を追記します

main.ts
app.setGlobalPrefix('api/v1');

Swaggerのjsonを生成するファイルを作成します。
@nestjs/swaggerのデコレータやDocumentBuilderで設定したconfigの内容からjsonを生成しています。
詳しくは下記を参照してください
https://docs.nestjs.com/openapi/introduction

generate-open-api-json.ts
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定義に反映されるようになります。

nest-cli.json
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/swagger"]
  }
}

app.controllerにタグを追加します。(タグを設定しない場合、Step CIでテスト用のyml生成する際にエラーが発生しました)

app.controller.ts
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定義ファイルが生成されます。

packge.json
"swagger:export": "nest start --entryFile generate-open-api-json",

2.Step CIでテストをしてみる

Swagger定義ファイルの準備ができたのでStep CIでテストをします。
stepciのinstall

$ npm install stepci

packge.jsonにscript追加

packge.json
"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が生成されますが、オプションで設定可能です。
https://docs.stepci.com/reference/cli.html#generate-spec-path

$ 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のレスポンスが返ってくるかのテストが生成されていますね。

workflow.yml
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定義をより詳細に記載することで、詳細なテストができると嬉しいですね。
https://docs.stepci.com/guides/testing-http.html

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 --from=builder / /
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を作成し、テストが実行されていることが確認できました。

result

まとめ

今回はNestJS、Step CI、GitHub Actionsを使ってAPIのテストをCIで実行してみました。
CIでAPIのテストをするのは初めてでしたが、Step CIはSwagger定義があればコマンドを実行するだけなので導入も簡単でした。
今後ももう少し触ってみて、より詳細なテストができないか試してみようと思います。

採用について

弊社では一緒に開発する仲間を絶賛募集中です!
https://stafes.notion.site/stafes/d0996a00d77d418280982797c7e16001
少しでも気になった方がいれば、気軽にご相談・ご応募お待ちしております!
ウェルウェルカムカムです!

スタフェステックブログ

Discussion