Node.js と Mocha フレームワークを用いたテスト実行

読む時間の目安: 6 分

前提条件

開発向けコンテナーの利用 において、イメージビルドを行いコンテナー化アプリケーションを実行していること。

はじめに

最近のソフトウェア開発においてテストは重要な工程です。 テストは、さまざまな開発チームによって実に多くのことを実施します。 ユニットテスト、結合テスト、総合テスト(end-to-end testing)です。 本ガイドでは Docker においてユニットテストを実行していきます。 前提として、アプリケーション内の./testフォルダーには Mocha テストが定義されているものとします。

テストの生成

そこでアプリケーション内の./testディレクトリに Mocha テストを定義します。

$ mkdir -p test

以下のコードを./test/test.jsとして保存します。

var assert = require('assert');
describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal([1, 2, 3].indexOf(4), -1);
    });
  });
});

アプリケーションのローカル実行とテスト

Docker イメージをビルドして正常動作することを確認します。 以下のコマンドを入力して、Docker イメージをコンテナーとして実行します。

$ docker-compose -f docker-compose.dev.yml up --build

そこでアプリケーションに対して JSON データを POST して、HTTP GET リクエストを取得します。 これによって JSON データが正しく返ってくることを確認します。

$ curl --request POST \
  --url http://localhost:8000/test \
  --header 'content-type: application/json' \
  --data '{"msg": "testing"}'

同じエンドポイントに対して GET リクエストを実行して、JSON データが正しく返されることを確認します。 以下において「id」や「createDate」は、処理環境によって異なります。

$ curl http://localhost:8000/test

{"code":"success","payload":[{"msg":"testing","id":"e88acedb-203d-4a7d-8269-1df6c1377512","createDate":"2020-10-11T23:21:16.378Z"}]}

Mocha のインストール

以下のコマンドを実行して Mocha をインストールします。 そして開発用の依存パッケージを追加します。

$ npm install --save-dev mocha

テスト実行のための package.json と Dockerfile の変更

さて、アプリケーションは正しく動作することがわかりました。 そこでコンテナー内部においてテストを実行してみます。 先ほどと同じ docker run コマンドを用いますが、ただし今回の場合、コンテナー内部にある CMD をオーバーライドして npm run test を実行するようにします。 このコマンドは、package.json ファイルの「script」セクションのもとにあるコマンドを起動します。 以下を見てください。

{
...
  "scripts": {
    "test": "mocha ./**/*.js",
    "start": "nodemon --inspect=0.0.0.0:9229 server.js"
  },
...
}

そして以下の Docker コマンドによって、コンテナーを起動してテストを実行します。

$ docker-compose -f docker-compose.dev.yml run notes npm run test
Creating node-docker_notes_run ...

> node-docker@1.0.0 test /code
> mocha ./**/*.js



  Array
    #indexOf()
      ✓ should return -1 when the value is not present


  1 passing (11ms)

テスト用 Dockerfile のマルチステージ化

テスト起動をコマンドから行うことに加えて、イメージビルドも同じものから行っていきます。 つまりマルチステージビルドによる Dockerfile を利用します。 以下に示す Dockerfile は、テストの実行と本番環境用イメージのビルドを行います。

# syntax=docker/dockerfile:1
FROM node:14.15.4 as base

WORKDIR /code

COPY package.json package.json
COPY package-lock.json package-lock.json

FROM base as test
RUN npm ci
COPY . .
CMD [ "npm", "run", "test" ]

FROM base as prod
RUN npm ci --production
COPY . .
CMD [ "node", "server.js" ]

ここではまずはじめにFROM node:14.15.4行に対して、as baseによりラベルをつけます。 これを行うと、このビルドステージを他のビルドステージにおいて参照することができます。 次に新たなビルドステージに対して test というラベルをつけます。 このステージはテスト実行用に利用します。

そこでイメージを再ビルドしてテストを実行してみます。 ここで実行する docker build コマンドは上と同様ですが、今回は--target testというフラグをつけます。 これによってテストビルドステージを指定してビルドします。

$ docker build -t node-docker --target test .
[+] Building 66.5s (12/12) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                 0.0s
 => => transferring dockerfile: 662B                                                                                                                                 0.0s
 => [internal] load .dockerignore
 ...
  => [internal] load build context                                                                                                                                    4.2s
 => => transferring context: 9.00MB                                                                                                                                  4.1s
 => [base 2/4] WORKDIR /code                                                                                                                                         0.2s
 => [base 3/4] COPY package.json package.json                                                                                                                        0.0s
 => [base 4/4] COPY package-lock.json package-lock.json                                                                                                              0.0s
 => [test 1/2] RUN npm ci                                                                                                                                            6.5s
 => [test 2/2] COPY . .

テストイメージがビルドできたので、これをコンテナー内において実行しテストが実施できるかどうかを見てみます。

$ docker run -it --rm -p 8000:8000 node-docker

> node-docker@1.0.0 test /code
> mocha ./**/*.js



  Array
    #indexOf()
      ✓ should return -1 when the value is not present


  1 passing (12ms)

ビルド処理の結果は上では省略しましたが、Mocha test runner の実行は完了し、テストが正常終了していることがわかります。

うまくできましたが、これでもイメージのビルドとテスト実行に対して 2 つの docker コマンドを実行しなければなりません。 これを少しだけ改善します。 テストステージにおいて CMD 命令ではなく RUN 命令を使うようにします。 CMD 命令はイメージビルド時には実行されません。 これが実行されるのはコンテナー内においてイメージが実行されたときです。 一方 RUN 命令の場合は、イメージビルドの最中にテストが実行され、テストが失敗したときにはビルドが停止します。

以下の Dockerfile においてハイライト表示されている行を修正してください。

# syntax=docker/dockerfile:1
FROM node:14.15.4 as base

WORKDIR /code

COPY package.json package.json
COPY package-lock.json package-lock.json

FROM base as test
RUN npm ci
COPY . .
RUN npm run test

FROM base as prod
RUN npm ci --production
COPY . .
CMD [ "node", "server.js" ]

テストを実行するには、先ほどと同様に docker build コマンドを実行する必要があります。

$ docker build -t node-docker --target test .
[+] Building 8.9s (13/13) FINISHED
 => [internal] load build definition from Dockerfile      0.0s
 => => transferring dockerfile: 650B                      0.0s
 => [internal] load .dockerignore                         0.0s
 => => transferring context: 2B

> node-docker@1.0.0 test /code
> mocha ./**/*.js



  Array
    #indexOf()
      ✓ should return -1 when the value is not present


  1 passing (9ms)

Removing intermediate container beadc36b293a
 ---> 445b80e59acd
Successfully built 445b80e59acd
Successfully tagged node-docker:latest

ここでもわかりやすくなるように、ビルド結果の出力は省略しましたが、テストの実行と正常終了が見てとれます。 そこでテストが失敗したときにはどうなるかを見るために、ブレークポイントを設定して出力結果を確認します。

test/test.js ファイルを開いて 5 行めを以下のように変更します。

     1	var assert = require('assert');
     2	describe('Array', function() {
     3	  describe('#indexOf()', function() {
     4	    it('should return -1 when the value is not present', function() {
     5	      assert.equal([1, 2, 3].indexOf(3), -1);
     6	    });
     7	  });
     8	});

そして先ほどと同じ docker build コマンドを実行します。 ビルド処理は失敗して、失敗したテストに関する情報がコンソールに出力されます。

$ docker build -t node-docker --target test .
Sending build context to Docker daemon  22.35MB
Step 1/8 : FROM node:14.15.4 as base
 ---> 995ff80c793e
...
Step 8/8 : RUN npm run test
 ---> Running in b96d114a336b

> node-docker@1.0.0 test /code
> mocha ./**/*.js



  Array
    #indexOf()
      1) should return -1 when the value is not present


  0 passing (12ms)
  1 failing

  1) Array
       #indexOf()
         should return -1 when the value is not present:

      AssertionError [ERR_ASSERTION]: 2 == -1
      + expected - actual

      -2
      +-1

      at Context.<anonymous> (test/test.js:5:14)
      at processImmediate (internal/timers.js:461:21)



npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! node-docker@1.0.0 test: `mocha ./**/*.js`
npm ERR! Exit status 1
...

次のステップ

本節では Docker イメージのビルド処理の一部分として、テスト実行を行いました。

次節では GitHub アクションを使って CI/CD パイプラインを設定する方法を見ていきます。 以下を参照してください。

CI/CD の設定

フィードバック

本トピック改善のためにフィードバックをお寄せください。 お気づきの点があれば Docker Docs の GitHub リポジトリに issue をあげてください。 あるいは PR の生成 により変更の提案を行ってください。


Node.js, build, Mocha, test