2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

VSCodeでイマドキのJavaScriptの開発環境をサクッと作ろうぜ(自動テスト編)

Last updated at Posted at 2022-05-15

はじめに

イマドキのJavaScript開発環境記事第2弾。

第1弾はこちら

今回は、自動テストの設定を作っていく。

Jestの自動テスト

JavaScriptの自動テストは有名どころがいくつかあるが、最近はJestが良い感じっぽいので、これを使うための設定を行っていく。

全体のファイル構成

今回は少し複雑なので、ファイル構成から俯瞰しよう。
テスト用コードは、Jestの推奨である__test___配下に配置する。メインのコードはcontents配下だ。

<プロジェクトルート>
├── .jestrc.js
├── __tests__
│   ├── __snapshots__
│   │   └── employeeMain.spec.js.snap
│   ├── employeeMain.spec.js
│   └── mock
│       └── mockAxios.js
├── contents
│   ├── app.js
│   ├── employeeMain.vue
│   └── mixin.js
└── package.json

Jest関連モジュールのインストール

まずは前回同様、以下をpackage.jsonに追記して、npm installしよう。
例によって、バージョン指定が面倒なので、今動いているバージョンを書いておく。

package.json(抜粋)
  "devDependencies": {
    "axios-mock-adapter": "^1.20.0",
    "babel-jest": "^26.6.3",
    "flush-promises": "^1.0.2",
    "jest": "^26.6.3",
    "jest-serializer-vue": "^2.0.2",
    "vue-jest": "^4.0.1",
    "vue-loader": "^15.9.8",
    "vue-server-renderer": "^2.6.14",
  }

Jest設定ファイルの配置

Jestの設定ファイルはデフォルトはjest.config.jsというファイル名だが、この手の設定ファイルはツールによってバラバラで分かりにくいので、今回の一連の記事では、ドット始まりで統一する。

.jestrc.js
module.exports = {
  verbose: true,
  transform: {
    '^.+\\.js$': '<rootDir>/node_modules/babel-jest',
    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
  },
  // テスト対象の拡張子を列挙する
  moduleFileExtensions: [
    'js',
    'vue',
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1',
  },
  testPathIgnorePatterns: [
    '/mock/',
    '/__snapshots__/'
  ],
  // スナップショットの設定
  // カスタムシリアライザを使用して読みやすいスナップショットを表示する
  // 改善されたスナップショットをJestのスナップとして渡す
  snapshotSerializers: [
    '<rootDir>/node_modules/jest-serializer-vue'
  ],
  // カバレッジの設定
  collectCoverage: true,
  collectCoverageFrom: [
    '**/*.{js,vue}',
    // 設定ファイルを除外
    '!**/.*',
    // 外部モジュールを除外
    '!**/node_modules/**',
    '!**/mock/**'
  ],
  coverageReporters: [
    'text'
  ]
}

なお、標準と異なる設定ファイルを用いる場合は、--configオプションでファイルを指定する。package.jsonに以下の設定を追加しよう。

package.json(抜粋)
  "scripts": {
+   "test": "jest --config ./.jestrc.js",
  }

axios-mock-adapterでAxiosをモックする

さて、Vue.jsでSPAを作ろうとしている以上、このスクリプト単独で何かをするというよりも、サーバAPIと連動することの方が多いだろう。
Jest+Axiosでは、Jestの設定のtestEnvironmentがデフォルトのjsdomだとネットワークエラーが出てしまいうまく動作しない。
一方で、これをnodeに変更してしまうと、Vue.jsがjsdomモードでないと動作しないため、うまく動かなくなってしまう。

そのための設定が以下である。

package.json(抜粋)
  "devDependencies": {
+   "axios-mock-adapter": "^1.20.0",
    "babel-jest": "^26.6.3",
    "flush-promises": "^1.0.2",
    "jest": "^26.6.3",
    "jest-serializer-vue": "^2.0.2",
    "vue-jest": "^4.0.1",
    "vue-loader": "^15.9.8",
    "vue-server-renderer": "^2.6.14",
  }

モック化する情報を以下のように設定する。
returnする第2要素は、何でもよいのでモックで応答したい内容を記載する。

mock/mockAxios.js
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'

const mockAxios = new MockAdapter(axios)

mockAxios.onGet(process.env.APIGATEWAY_INVOKE_URL + '/employee').reply(config => {
  if (config.params.id === '00001') {
    return [
      200,
      [
        { department: '001002', id: '00001', age: '36', dept_id: '001', dept_name: '開発部', sect_id: '002', sect_name: '第二開発課' },
        { department: '002001', id: '00001', age: '36', dept_id: '002', dept_name: '総務部', sect_id: '001', sect_name: '総務課' },
        { department: '007001', id: '00001', age: '36', dept_id: '007', dept_name: '製造部', sect_id: '001', sect_name: '第一製造課' }
      ]
    ]
  }
})

あとは、これをテスト対象のspec.jsファイルから、import '@/__tests__/mock/mockAxios.js'とでもして呼び出せば良い。

モックしたAxiosがうまく同期できない場合、flush-promisesを使う

Vue.jsの中でasync~awaitしても、Jest側でうまく同期できないことがある。
その場合は、flush-promisesのモジュールを使おう。

package.json(抜粋)
  "devDependencies": {
    "axios-mock-adapter": "^1.20.0",
    "babel-jest": "^26.6.3",
+   "flush-promises": "^1.0.2",
    "jest": "^26.6.3",
    "jest-serializer-vue": "^2.0.2",
    "vue-jest": "^4.0.1",
    "vue-loader": "^15.9.8",
    "vue-server-renderer": "^2.6.14",
  }

上記のモジュールをインストールした後に、

test.spec.js(抜粋)
import flushPromises from 'flush-promises'

// (中略)

  // 待ち合わせしたい箇所
  await flushPromises()

とすることで、一旦非同期で動作しているところをすべて待ち合せることができる。

jest-serializer-vueでスナップショットテストを行う

スナップショットテストとは、前回のレンダリングしたHTMLを保存しておき、それと今回のテスト結果を比較することでデグレードしていないか確認できるテストだ。
Jest+Vue.jsでは以下のモジュールを使用する。

package.json(抜粋)
  "devDependencies": {
    "axios-mock-adapter": "^1.20.0",
    "babel-jest": "^26.6.3",
    "flush-promises": "^1.0.2",
    "jest": "^26.6.3",
+   "jest-serializer-vue": "^2.0.2",
    "vue-jest": "^4.0.1",
    "vue-loader": "^15.9.8",
+   "vue-server-renderer": "^2.6.14",
  }

設定ファイルで以下の部分がスナップショットテストの設定に該当する。

.jestrc.js(抜粋)
module.exports = {
// (中略)
+ // スナップショットの設定
+ // カスタムシリアライザを使用して読みやすいスナップショットを表示する
+ // 改善されたスナップショットをJestのスナップとして渡す
+ snapshotSerializers: [
+   '<rootDir>/node_modules/jest-serializer-vue'
+ ],
// (中略)
}

テストコードでは、任意の場所でスナップショットを取ることが可能だ。
それぞれのテストの最後にスナップショットを取るのが無難ではないかと考えられる。

test.spec.js(抜粋)
// テスト対象のモジュール
import employeeMain from '@/contents/employeeMain.vue'
+ // スナップショットテスト用
+ import { createRenderer } from 'vue-server-renderer'

// テストスイート
describe('employeeMain', () => {
//(中略)
+ afterEach(() => {
+   const renderer = createRenderer()
+   renderer.renderToString(wrapper.vm, (err, str) => {
+     if (err) throw new Error(err)
+     // 最新のスナップショットと一致するか比較
+     expect(str).toMatchSnapshot()
+   })
+ })
//(中略)
}

なお、スナップショットテストで、コード修正によって差分が出るようになるのが正しいケースもある。
このため、スナップショットを更新したい場合は、-uオプションで更新を行う。
以下のようにpackage.jsonに追記をして、更新可能にしておこう。
なお、CI/CDで用いるためには、この結果の__snapshot__もリポジトリに登録するようにしておこう。

package.json(抜粋)
  "scripts": {
    "test": "jest --config ./.jestrc.js",
+   "testUpdate": "jest --config ./.jestrc.js -u",
  }

カバレッジの設定

カバレッジは、標準のJestの機能として提供されているため、追加のモジュールは必要ない。
以下の設定を入れておこう。

設定ファイルで以下の部分がスナップショットテストの設定に該当する。

.jestrc.js(抜粋)
module.exports = {
// (中略)
+ // カバレッジの設定
+ collectCoverage: true,
+ collectCoverageFrom: [
+    '**/*.{js,vue}',
+    // 設定ファイルを除外
+    '!**/.*',
+    // 外部モジュールを除外
+    '!**/node_modules/**',
+    '!**/mock/**'
+ ],
+ coverageReporters: [
+   'text'
+ ],
// (中略)
}

これを設定することで、テスト結果で以下のようなカバレッジレポートを出力可能だ。

----------------------|---------|----------|---------|---------|-------------------
File                  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------------|---------|----------|---------|---------|-------------------
All files             |   46.29 |     37.5 |      40 |   46.29 |                   
 app.js               |       0 |      100 |     100 |       0 | 4                 
 employeeMain.vue     |   52.38 |        0 |      60 |   52.38 | 44-50,73-80       
 mixin.js             |     100 |      100 |     100 |     100 |                   
----------------------|---------|----------|---------|---------|-------------------

今回作ったモックや設定ファイルがレポートに巻き込まれないよう、collectCoverageFromでしっかり指定をしよう。

さて、これで、npm run testで自動テストが走らせられるようになった!
次回はいよいよWebpackでバンドルしてデプロイを行おう!

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?