TypeScriptのjestでクラスの関数をモックする方法

ReactNativeアプリに単体テストを導入しはじめました。
jest初めてですがクラスの関数のモック方法がわかったのでこのエントリで使い方を紹介します。
基本的に [公式](https://jestjs.io/docs/ja/es6-class-mocks)のドキュメントを読めば理解できますが、いつくかハマりもありました。

この例では `HogeDataStore` というクラスを使っている `HogeHelper` というクラスをテストすることにします。

# テスト対象のコード
データストアはAPIやDBからデータ取得を想定しています。

```typescript|HogeDataStore.ts
export default class HogeDataStore {
  // jest.mock('./HogeDataStore')でundefinedを返すように自動でモック化される
  getFirst (): string {
    // 実際はDBやAPIを使う複雑なもの
    return 'some value'
  }

  // アロー関数なのでjest.mock('./HogeDataStore')ではモック化されず欠損する
  getSecond = (): string => {
    // 実際はDBやAPIを使う複雑なもの
    return 'some value'
  }
}
```

ヘルパーはデータストアと連携し取得したデータを加工したり、値の判定をしたりするものとします。

```typescript|HogeHelper.ts
import HogeDataStore from './HogeDataStore'

export default {
  // データストアの1つ目のデータがhogeか判別する
  isFirstHoge: (): boolean => {
    const dataStore = new HogeDataStore()
    const value = dataStore.getFirst()
    console.log(`first is ${value}`)
    return value === 'hoge'
  },

  // データストアの2つ目のデータがhogeか判別する
  isSecondHoge: (): boolean => {
    const dataStore = new HogeDataStore()
    const value = dataStore.getSecond()
    console.log(`second is ${value}`)
    return value === 'hoge'
  }
}
```

# テストコード
```typescript|HogeHelper.test.ts
import HogeHelper from './HogeHelper'
import { mocked } from 'ts-jest/utils'
import HogeDataStore from './HogeDataStore'

// 自動モック
// アロー関数は欠落し、通常の関数はundefinedを返すようになる
// [注意jest.mock()はテストケースの外のブロックで実行する必要がある
jest.mock('./HogeDataStore')

describe('HogeHelper', () => {
  beforeEach(() => {
    // モックを初期化
    mocked(HogeDataStore).mockClear()
  })

  it('case 1', function () {
    // 自動モックによりgetFirstがundefinedを返すようになったので、
    // getFirstの戻り値を'hoge'を返すようにモック化
    mocked(HogeDataStore).mockImplementation(
      (): any => {
        return {
          getFirst: (): string => {
            return 'hoge'
          }
        }
      }
    )
    // HogeHelper内でモック化されたgetFirst関数が使用される
    const isFirstHoge = HogeHelper.isFirstHoge()
    console.log(isFirstHoge)
    expect(isFirstHoge).toBe(true)
  })

  it('case 2', () => {
    // 自動モックによりgetSecondが欠損したので、
    // getSecondの戻り値を'hoge'を返す関数でモック化
    mocked(HogeDataStore).mockImplementation(
      (): any => {
        return {
          getSecond: (): string => {
            return 'hoge'
          }
        }
      }
    )
    // HogeHelper内でモック化されたgetSecond関数が使用される
    const isSecondHoge = HogeHelper.isSecondHoge()
    expect(isSecondHoge).toBe(true)
  })
})
```

## ハマり1
`jest.mock('./HogeDataStore')` は`it`の中で書いても意味がなかったです。 `describe`の外に書く必要があるみたいです。

## ハマり2
アロー関数である`getSecond`は`jest.mock`を実行後はアクセスできなくなります。
`getSecond`を使用するには明示的にモック化する必要があります。
`getFirst`はアロー関数でないので自動モックによりundefinedを返す関数になります。

## ハマり3
`package.json` の `jest` 項目にこんな感じで色々設定する必要があります。
[ここ](https://medium.com/@jan.hesters/3-easy-steps-to-set-up-react-native-with-typescript-jest-and-enzyme-592ca042262f) を参考にしました。


```
  "jest": {
    "preset": "react-native",
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
      "^.+\\.tsx?$": "ts-jest"
    },
    "transformIgnorePatterns": [
      "<rootDir>/node_modules/*\\.js",
      "<rootDir>/node_modules/(?!native-base)/"
    ],
    "globals": {
      "ts-jest": {
        "tsConfig": "tsconfig.jest.json"
      }
    },
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ],
    "modulePaths": [
      "<rootDir>"
    ],
    "setupFiles": [
      "./tests/setup.js"
    ]
  }
```