swagger-nodeとDockerで簡単にモックサーバーを構築する


はじめに

APIを呼び出す機能をテストする際に、とりあえずモックサーバーを立てたい。
そんなときに便利なのが、SwaggerのNode.js 製のモジュールであるswagger-nodeです。

SwaggerでAPIを定義しておくだけで、モックサーバーを起動できます。
便利なのは、自前でテストデータを用意しなくても、データ型(string, number, boolean, array, object etc)に応じた適当な値をレスポンスしてくれる点です。

Swaggerの概要やswagger-nodeの使い方はこちらで紹介されているため、詳細は割愛します。
Swaggerとswagger-node

この記事では、Node.jsのインストールが面倒なのと、環境を汚さずに使いたいということで、
swagger-nodeとDockerを組み合わせてモックサーバーを構築する方法を説明します。

前提

dockerとdocker-composeを利用できること

モックサーバーを構築する

Dockerfileの作成

まずはswagger-nodeを内包するDockerイメージを作成します。
最新のLTS版であるNode.js v12だとエラーになるようなので、v10を使用します。
https://github.com/swagger-api/swagger-node/issues/586
npm install --save swagger-routerすればOKとの情報もありましたが、未確認です。

適当なディレクトリでDockerfileを作成します。

FROM node:10

# アプリケーションディレクトリを作成する
WORKDIR /usr/src/app

# swagger-nodeをグローバルインストールする
RUN npm install swagger -g

ENTRYPOINT ["/bin/sh", "-c", "while :; do sleep 10; done"]

これをビルトしてDockerイメージを作成します。

$ docker build . -t swagger-node

swaggerプロジェクトの作成

swaggerプロジェクトを作成するために、swagger-nodeをインストールしたコンテナを起動して、swaggerコマンドを実行します。
作成したswaggerプロジェクトはホストにマウントするために、-v $PWD:/usr/src/appオプションを付けておきます。

$ docker run -d -v $PWD:/usr/src/app --name first-swagger-container swagger-node
$ docker exec -it first-swagger-container swagger project create sample-project
? Framework?
  connect
❯ express
  hapi
  restify
  sails

作成されたsample-projectはこのような構成になります。
swagger.yamlがAPI定義ファイルです。

sample-project/
 ├ api/
 │ ├ controllers/
 │ ├ helpers/
 │ ├ mocks/
 │ └ swagger
 │   └ swagger.yaml
 ├ config/
 ├ node_modules/
 ├ test/
 ├ .gitignore
 ├ app.js
 ├ package-lock.json
 ├ package.json
 └ README.md

なお、ここで作成したコンテナは不要なので削除します。

$ docker rm --force first-swagger-container

Dockerコンテナ起動

同じディレクトリにdocker-compose.yamlを作成して、swagger-mock(mockサーバーコンテナ)とswagger-editor(swagger.yamlを編集するためのコンテナ)を定義します。

docker-compose.yaml
version: "3.6"
services:
  swagger-mock:
    build: .
    environment:
      - CHOKIDAR_USEPOLLING=true
    ports:
      - "10010:10010"
    volumes:
      - .:/usr/src/app
    networks:
      examples-net:
        ipv4_address: 172.16.239.103
    # -m: モックモードとして起動するオプション
    entrypoint: bash -c "cd sample-project && swagger project start -m"
  swagger-editor:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/usr/src/app
    networks:
      examples-net:
        ipv4_address: 172.16.239.104
    # -s:     起動時にブラウザが立ち上がらないようにする
    # -p:     指定しない場合ポートがランダムになるため8000で固定する
    # --host: ipv4_addressで指定したIPを設定する
    entrypoint: bash -c "cd sample-project && swagger project edit -s -p 8000 --host 172.16.239.104"
networks:
  examples-net:
    name: examples-net
    driver: bridge
    ipam:
      driver: default
      config:
      - subnet: 172.16.239.0/24

swagger-mockのポイントとしては、CHOKIDAR_USEPOLLING=trueを設定している点です。
これは、マウントされているファイルが更新された際にコンテナを自動起動するための設定です。
これによって、swagger.yamlが更新された際に即時にモックサーバーに反映されるようになります。

  • 補足
    • swagger-nodeには、nodemonというモジュールが組み込まれており、ファイル更新を検知したら自動で再起動する仕組み(いわゆるホットリロード)が実装されています。 しかし、Dockerと組み合わせた場合にこれが効かなくなってしまい、Dockerコンテナとしてホットリロードする方法を採用しました。

それではDockerコンテナを起動してみます。

$ docker-compose up -d

コンテナが起動するとswagger-mockがモックサーバーとして利用できる状態になります。
試しにhttp://<コンテナのIPアドレス>:10010に対して、hello API(初回作成時のサンプルAPI)を呼ぶと、レスポンスを返してくれます。

$ curl  http://192.168.99.100:10010/hello?name=Scott
{"message":"Sample text"}

swagger.yamlを更新

目的とするAPIのモックサーバーを起動するには、swagger.yamlを編集する必要があります。
そこでswagger-editorコンテナを使用します。
ブラウザでhttp://<コンテナのIPアドレス>:8000にアクセスするとswagger.yamlの編集画面が開きます。

試しに新しいAPIとして次のようにuser API(Get, POST)を定義してみます。
左側の編集パネルに次のように追記します。

swagger.yaml
swagger: "2.0"
  version: "0.0.1"
  title: Hello World App
# during dev, should point to your local machine
host: localhost:10010
# basePath prefixes all resource paths 
basePath: /
# 
schemes:
  # tip: remove http to make production-grade
  - http
  - https
# format of bodies a client can send (Content-Type)
consumes:
  - application/json
# format of the responses to the client (Accepts)
produces:
  - application/json
paths:
  ...(略)...
  /user:
    x-swagger-router-controller: UserController
    get:
      operationId: getUser
      parameters:
        - name: id
          in: query
          required: true
          type: string
      responses:
        "200":
          description: Success
          schema:
            properties:
              name:
                type: string
              age:
                type: number
    post:
      operationId: postUser
      parameters:
        - name: "user"
          in: "body"
          required: true
          schema:
            required:
              - name
              - age
            type: object
            properties:
              name:
                type: string
              age:
                type: number
      responses:
        "200":
          description: Success
          schema:
            properties:
              message:
                type: string
  ...(略)...

swagger.yamlの書き方について、基本的にはSwaggerの構文に従えばOKです。
ここで重要なのはx-swagger-router-controllerに任意の値を設定することです。
これはswagger-node独自の項目で、ルーティングするコントローラー(.js)を設定するものです。
swagger project startコマンドで普通に起動する場合は、ここで設定したコントローラー(.js)にルーティングされ、実装された処理に従ってレスポンスが返されます。
モックサーバーと起動する(-mオプションを付与する)場合は、コントローラーが存在しなくても、responsesで定義している型に応じてレスポンスを返してくれるのです。

動作確認

ブラウザの編集画面に書き込んだ時点で、swagger.yamlが更新され、上述したホットリロードの設定によってswagger-mockコンテナが再起動されます。
つまり、即時にモックサーバーに反映されています。
自身でコンテナを再起動する必要はありません。

実際にuser APIを呼んでみます。

$ curl http://192.168.99.100:10010/user?id=xxx
{"name":"Sample text","age":1}

$ curl -X POST -H 'Content-Type:application/json' -d '{"id": "aaa", "name": "hoge", "age": 10}' http://192.168.99.100:10010/user
{"message":"Sample text"}

適当な値が設定されたレスポンスを返してくれました。

所感

APIを呼び出す機能をテストしたい場合、Swagger定義をもらって編集画面を開いてコピペして、x-swagger-router-controllerという若干の設定を行えば、簡単にモックサーバーを起動できます。

あとは、自分でAPIを定義する場合にも、「swagger.yamlを編集→API呼び出しして確認」というライフサイクルを回しやすそうです。

おわりに

Dockerを勉強中なので、無駄な手順が含まれているように思います。(特にswaggerプロジェクトを作成するあたり)
おいおい勉強して更新していきたいと思います。