jest初心者がつまづいたところまとめ

初めてjest(というかテストコード)を触る人に向けた、導入と考え方を、初心者なりにまとめます。

コツを抑えられればサクサクテスト記述できるのですが、それを掴むまで結構苦労したので、その知見を紹介できれば、と思います。

対象のソースコードはNode.jsとVue.jsです。

# はしがき

最近、結婚披露宴を行い、そこでスライドショーアプリを自作しました。
詳細は[こちら](https://yason0319.hatenablog.com/entry/2020/05/25/220926)。

このアプリはNode.jsとVue.jsで作っており、jestでテストコードを書いています。
本記事では、その時の知見をまとめました。

# JavaScriptテストコードの基本
テストコードの構造は基本的には以下のように書きます

```sample.test.js
describe('sample test', () => {
    test('test1', () => {
        // テスト処理
    })

   test('test2', () => {
        // テスト処理
    })
})
```

テスト時には、`test`関数内の処理が実行され、結果のチェックを行います。`describe`関数は複数の`test`をグルーピングします。
サンプルソース例を結構見てみましたが、`test`関数を直に書いているサンプルはなかったので、`describe`関数から定義するのがお作法なようです。

Jestの他にもJavaScriptのユニットテストフレームワークは存在します。`mocha+chai`、`karma+jasmine`、などが有名なようです。

ただ、今回テストを行うソースがNode.js環境だったので、親和性の高いJestを採用しています。

# Jest de テストコード - mock編

ユニットテストを行うにあたって、真っ先に障壁になるもの。
それは、`require`や`import`している他ファイルの処理です。

例えば、`fs`を`require`しているソースをテストする場合を考えます。操作するファイルが本番環境以外で取得できない(API経由で取得するなど)場合、テスト時に実際の`fs`の関数が実行されてしまうのはよろしくないです(ファイルがあったとしても関数を実行するべきではないですが)。

そこで、`require`される`fs`をモック化します。

### mockファイルの生成

npmパッケージをモック化する場合、`node_module`と同じディレクトリに`__mock__`という名前でディレクトリを作り、その中に`require`される名前と同じ名前のjsファイルを置きます。
`__mock__`ディレクトリが存在していた場合、テストコード実行時にjestはまず`__mock__`ディレクトリを探し、そこに定義がなければnpmパッケージの処理を実行します。ですので、`__mock__`内に定義しておけばモック化することができます。

```
your-project
├── node_modules
│   └── fs
└── __mock__
    └── fs
```

この`__mock__`配下のfsにモックコードを書きます。

下記はfs.mkdirSync関数のモック化例です。

```__mock__/fs.js
// モック元ファイルの指定
const fs = jest.genMockFromModule('fs')

// ディレクトリ作成関数mkdirSyncのモック記述
function mkdirSync(directoryPath) {
  var file = { directoryPath: 'dummy' }
}

// モック関数の登録
fs.mkdirSync = mkdirSync

module.exports = fs;
```

こうすることで、テスト対象コード内でfs.mkdirSyncの処理があった場合、モック化した関数のみを実行してくれます。

### 一部関数のみのモック化

ただ、わざわざmockファイルを作る必要はないケースというのもあります。
例えば、テスト対象コード内で一度だけしか呼ばれない別ファイルの関数の処理をmock化したいとか、一つのテストケース内でしか呼ばれないとか言った場合です。

以下は`myApp.js`というファイルのテストコード内で、`myModule.js`の`funcA`という関数を`sample test`というテストケースの中でのみモック化する場合のサンプルです。

```myApp.test.js
const myApp = require('../myApp')
import myModuleMock from 'DIRECTORY_PATH/myModule'

describe('all test', () => {
  test('sample test', () => {
    // mock関数の定義
    const spy = jest.spyOn(myModuleMock, 'funcA').mockImplementation(() => {
        return true
      })
    
    // テスト実行
    myApp.exec()
    
    // mock関数がテスト内でコールされたか
    expect(spy).toHaveBeenCalled()
  })
})
```

注意しなければいけないのが、`spyOn()`で登録した関数は、あくまで関数が呼ばれたかどうかの状態を監視する役目しか持たないということです。
つまり、ただ`spyOn()`しただけだと、実際の`myModule.funcA`もコールされてしまいます。
これをモック化するためには、`mockImplementation`の記述を追加し、モック処理を書く必要があります。
これで関数単位のモック化ができます。


# Jest de テストコード - Vue.js編

正直難しいものはないです。
まずはVueのテスト用パッケージインストールします。

```bash
npm install --save-dev @vue/test-utils
```

あとはテストコードにインポートして、以下の通り書けばVueコンポーネントのテストが実施できます。

以下は、`Start`という名前のコンポーネントのテストコードです。

```js
// 子コンポーネントがある場合、shallowMountで定義
import {shallowMount} from '@vue/test-utils'
import Start from '@/components/Start'

wrapper = shallowMount(Start, {
  mocks: { $router: router }
})

test('sample test', () => {
  // Vueインスタンスかどうか確認する
  expect(wrapper.isVueInstance()).toBeTruthy()
	
  // templateのDOM要素をclassで指定し、アクションを行う
  wrapper.find('.opening_content').trigger('click')

  // 関数実行する場合、.vm.funcでコール
  wrapper.vm.getImageJson()

  // data要素を取得
  wrapper.vm.$data.showNum

  // emitイベント呼び出し
  wrapper.emitted('startSlideShow')
})
```

他コンポーネントとのやり取りについては、インスタンス宣言時、モック化させることができます。