Dockerでjavascriptの自動テスト環境を作る

# 概要

プログラミングすることがめっきり少なくなったのですが、プログラミングはとても好きで、図書館でこちらの本を借りてきました。

<a href="https://www.amazon.co.jp/%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E9%96%8B%E7%99%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E5%85%A5%E9%96%80-%E4%BB%8A%E3%81%8B%E3%82%89%E3%81%A7%E3%82%82%E7%9F%A5%E3%81%A3%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88%E6%88%A6%E7%95%A5%E3%81%AE%E5%BF%85%E9%A0%88%E7%9F%A5%E8%AD%98-%E5%90%89%E4%BA%95-%E5%81%A5%E6%96%87-ebook/dp/B0BWR5GHMP/ref=monarch_sidesheet">フロントエンド開発のためのテスト入門 - Amazon | Kindle</a>

UNITテストの自動化がわかりやすく、とても勉強になりました。

そしてDockerコンテナでテスト環境を配れたりしたら素敵だなと思って、javascriptのUNITテスト環境をDockerコンテナで作ってみました。

UNITテストのツールは jest(https://jestjs.io/ja/) を導入しています。

# 最終的にできること
Dockerコンテナ内にjavascriptアプリのunitテスト（jest）の環境を作ります。

以下は、そのコンテナ内でのUNITテストの実行がPASSした結果です。
```terminal
root@edf58454f2f8:/usr/src/app# npm test

> docker_web_app@1.0.0 test
> jest

 PASS  ./sum.test.js
  ✓ adds 1 + 2 to equal 3 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.208 s
Ran all test suites.
```

# 前提
PCにDockerDesktopがインストールされていること。
node.js version 20をベースにしています。

# ３種類のnodejsのDockerイメージ
node.jsは大きく分けて以下3種類のDockerイメージがあります。
今回は、以下のとおりデファクトイメージのnode:\<version>を使うとよいです。


> node:\<version>
> これがデファクトイメージです。自分のニーズが何かわからない場合は、おそらくこれを使用するとよいでしょう。これは、使い捨てコンテナ (ソース コードをマウントし、コンテナを起動してアプリを起動する) としても、他のイメージを構築するためのベースとしても使用できるように設計されています。
>
> node: \<version> -alpine
>このイメージは、公式イメージで入手できる人気のあるAlpine Linux プロジェクトに基づいています。>Alpine Linux は、ほとんどのディストリビューション ベース イメージ (約 5MB) よりもはるかに小さいため、一般的にイメージがよりスリムになります。alpine
>
>node:\<version>-slim
>このイメージには、デフォルト タグに含まれる共通パッケージは含まれておらず、実行に必要な最小限のパッケージのみが含まれていますnode。イメージのみがデプロイされ、スペースの制約がある環境で作業している場合を除きnode、このリポジトリのデフォルトのイメージを使用することを強くお勧めします。
（引用元：https://hub.docker.com/_/node）


こちらにもnodeイメージの選定について同じ意見がありました。
> 近年、slimイメージは150MBまでサイズが小さくなっており、かつさまざまな状況で最もよく動作するようになっています。 Alpineはとても小さなコンテナディストリビューションで、最小のnodeイメージはたった75MBです。しかし、パッケージマネージャをaptからapkに取り替えたり、 特別な状況 に対応したり、 セキュリティスキャンの制限 を回避したりする努力をするくらいなら、ほとんどの場合node:alpineをお勧めしません。
（引用元：https://www.creationline.com/tech-blog/29422）

# 完成時のディレクトリ構成
適宜どこかにプロジェクトフォルダ（YOUR-PROJECT-FOLDER）を作成して、以降で以下のファイルを作っていきます。 

&emsp; YOUR-PROJECT-FOLDER
&emsp;├package.json  <font color="green">       -----npmでパッケージを管理するために使われる構成ファイル</font>
&emsp;├server.js     <font color="green">       -----Webアプリ</font>
&emsp;├Dockerfile     <font color="green">　　    -----Dockerfile</font>
&emsp;├.dockerignore  <font color="green">　  -----dockerイメージに含めないファイル</font>
&emsp;├sum.js   <font color="green">　          -----サンプルコード</font>
&emsp;└sum.test.js <font color="green">　 -----サンプルコード（sum.js）の単体テスト用のコード</font>

# 各ファイル

## package.json
https://nodejs.org/ja/docs/guides/nodejs-docker-webapp にあるpackage.jsonを編集し、"scripts"に"jest"を追加しています。
```json:package.json
{
    "name": "docker_web_app",
    "version": "1.0.0",
    "description": "Node.js + jest on Docker",
    "author": "First Last <first.last@example.com>",
    "main": "server.js",
    "scripts": {
        "start": "node server.js",
        "test": "jest"
    },
    "dependencies": {
        "express": "^4.16.1"
    },
    "devDependencies": {
        "jest": "^29.7.0"
    }
}
```
## Dockerfile
Dockerfileもhttps://nodejs.org/ja/docs/guides/nodejs-docker-webapp にあるDockerfileを編集して、
```Dockerfile
npm install --save-dev jest
```
追加すると、全体は次のような定義になります。
```Dockerfile:Dockerfile
#FROM node:12
FROM node:20
# アプリケーションディレクトリを作成する
WORKDIR /usr/src/app
# アプリケーションの依存関係をインストールする
# ワイルドカードを使用して、package.json と package-lock.json の両方が確実にコピーされるようにします。
# 可能であれば (npm@5+)
COPY package*.json ./
RUN npm install && \
    npm install --save-dev jest
# 本番用にコードを作成している場合
# RUN npm install --only=production
# アプリケーションのソースをバンドルする
COPY . .
EXPOSE 8080
CMD [ "node", "server.js" ]
```

## sample.js
サンプルのコードを用意します。今回は簡単な足し算にしてみました。
```javascript:sample.js
function sum(a, b) {
    return a + b;
  }
  module.exports = sum;
```
## sample.test.js
sample.jsのテストコードを作ります。サンプルコードの結果が「1+2=3にるか」を評価したUNITテストです。
```javascript:sample.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
```
## .dockerignore
これはなくてもよいのですが、dockerに無視して欲しいファイルを記載しています。
```docker:.dockerignore
node_modules
npm-debug.log
```
## dockerイメージのbuild
プロジェクトフォルダ内（以下ではdocker-webapp-jest）に移動して、作成したDockerfileでDockerイメージをビルドします。ビルドコマンドは、
```
docker build . -t ryu/node-web-app 
```
としています。オプション（-t）は、 名前:タグ の形式で指定しますが、名前のみつけて、ryu/node-web-app としました

```terminal
docker-webapp-jest % docker build . -t ryu/node-web-app          
[+] Building 74.3s (10/10) FINISHED                                                                                                                                  
 => [internal] load build definition from Dockerfile                                                                                                            0.1s
 => => transferring dockerfile: 37B                                                                                                                             0.0s
 => [internal] load .dockerignore                                                                                                                               0.0s
 => => transferring context: 34B                                                                                                                                0.0s
 => [internal] load metadata for docker.io/library/node:12                                                                                                      7.7s
 => [1/5] FROM docker.io/library/node:12@sha256:01627afeb110b3054ba4a1405541ca095c8bfca1cb6f2be9479c767a2711879e                                                0.0s
 => [internal] load build context                                                                                                                               0.0s
 => => transferring context: 688B                                                                                                                               0.0s
 => CACHED [2/5] WORKDIR /usr/src/app                                                                                                                           0.0s
 => [3/5] COPY package*.json ./                                                                                                                                 0.0s
 => [4/5] RUN npm install &&      npm install --save-dev jest                                                                                                  65.9s
 => [5/5] COPY . .                                                                                                                                              0.0s
 => exporting to image                                                                                                                                          0.5s
 => => exporting layers                                                                                                                                         0.5s
 => => writing image sha256:9b90a3ebd100cb3bea82c80b97caa4f8b5d3238bd7711c87004328bb1a0169fe                                                                    0.0s 
 => => naming to docker.io/ryu/node-web-app   

```

# Dockerの起動
ビルドしたイメージでDockerコンテナを起動します。
外部ポートは49161をコンテナ内の8080にポートフォワードします。
```terminal
docker-webapp-jest %  % docker run -p 49161:8080 -d ryu/node-web-app
```

#　Dockerコンテナの起動を確認
コンテナの起動をdocker psコマンドで確認します。STATUSがUPとなればOKです。
```terminal
docker-webapp-jest %docker ps       
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                         NAMES
77f093b9b8ab   ryu/node-web-app   "docker-entrypoint.s…"   34 seconds ago   Up 32 seconds   0.0.0.0:49161->8080/tcp, :::49161->8080/tcp   elegant_dirac
```

# 外部からコンテナ内のWebアプリにアクセスできることをcurlで確認

```terminal
docker-webapp-jest % curl -i localhost:49161
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 11
ETag: W/"b-Ck1VqNd45QIvq3AZd8XYQLvEhtA"
Date: Sat, 07 Oct 2023 04:30:39 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Hello World% 
```
Hello Worldが出力され、外部からWebアクセスできました。

# Dockerコンテナに入る
docker execコマンドでDockerコンテナ内に入ります。-it　オプション指定し、コンテナIDを指定(この例では77f093b9b8ab)します。
コンテナ内ではnpm testコマンドでjestを実行します。
```
docker-webapp-jest  % docker exec -it 77f093b9b8ab　/bin/bash
root@77f093b9b8ab:/usr/src/app# npm test

> docker_web_app@1.0.0 test
> jest

 PASS  ./sum.test.js
  ✓ adds 1 + 2 to equal 3 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.208 s
Ran all test suites.

```
無事にUNITテストを実行できました。