TypeScript + ReactNative + Jest(ts-jest)でテスト環境を作る

## はじめに
ts-jestの導入に苦戦していたので備忘のために記事を書きます。
初っ端の導入〜サンプルコードを動かすところまで。

### node_modulesバージョン
`package.json`で定義したモジュールバージョン抜粋は以下の通り。

``` json:package.json
{
	"dependencies": {
		"@types/jest": "^24.0.19", // devDependencies側で良い筈
		"@types/react-native": "^0.57.2",
		// 中略
	},
	"devDependencies": {
		"babel-jest": "^22.1.0",
		"jest": "22.4.2",
		"ts-jest": "22.0.1",
		"typescript": "2.8.4",
		"react-native": "0.57.1",
		// 中略
	},
	// 中略
}
```
JavaScriptの変遷を全然わかってない不勉強人間ですが`ES6`使ってますとメモ。

### 参考記事様方
いつもいつもありがとうございます。沢山拝見させて頂きました…。

初めましてJESTさんということでお世話になりました。
- [Facebook製のJavaScriptテストツール「Jest」の逆引き使用例](https://qiita.com/chimame/items/e97883fd46b67529d59f) -> テストコードサンプルはこちらに記載のものを使わせて頂きました。
- [Test on ReactNative - Jestの準備](https://qiita.com/yuri_RN/items/9f14d1f1d9f41e37fe4b)
- [TypeScript のテストを Jest (ts-jest) でやってみる](https://qiita.com/mangano-ito/items/99dedf88d972e7e631b7)
- [TypeScriptでJestを使うときの設定（ts-jest, @types/jestなど）](https://dackdive.hateblo.jp/entry/2019/04/15/000000)
- https://jestjs.io/docs/en/getting-started
- https://deltice.github.io/jest/docs/ja/getting-started.html


Babelも使っているのですが社内で標準化済のものを使っていたので`babel.config.js`というものを今更知るという(`.babelrc`使っていた)
- [今更のバベる。 Babel 7を試してみたメモ](https://chaika.hatenablog.com/entry/2018/11/21/150000)

まさに同じエラーに苦しんだ…
- [react-native+jestで、babelのエラーに遭遇「Support for the experimental syntax 'classProperties' isn't currently enabled」](https://qiita.com/kaba/items/3981fd974c1ca6da6d07)
- [classProperties isn't currenty enabled #21075](https://github.com/facebook/react-native/issues/21075)
- ["Cannot find name 'jest'" Typescript error](https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000368020--Cannot-find-name-jest-Typescript-error?sort_by=created_at)


色々と苦戦はしていたのですが完成形から。

## セットアップ
### 1. パッケージ導入
私の環境では不足していたのは`@types/jest`のみだったのでこちらを導入。`--dev`で良いと思うけれど一度詰まったのでここでは`dependencies`に入れます。
`jest`,`ts-jest`(と、多分`babel-jest`も)が入っていなければこちらも導入すること。

``` console:console
yarn add @types/jest
```

### 2. `package.json`・`tsconfig.json`に設定を記載
ソースコードの階層は以下のようにしています。

```
root
 └- src
    └- main
        └- App.tsx, index.ios.tsx, 他mainソースコード
    └- test
        └- テストコード
 └- .babelrc, package.json, tsconfig.jsonなど
```

最終的に追加した設定は以下の通り。

``` json:package.json
{
	"scripts": {
		// yarn testコマンドを利用できるようにする
		"test": "jest"
	},
	"jest": {
		"preset": "react-native",
		"transform": {
			// 後述:エラーケース① 対応
			"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
			// 後述:エラーケース② 対応
			"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
		},
		"testMatch": [
			"**/test/**/*.test.tsx"
		],
		"moduleFileExtensions": [
			"tsx",
			"js"
		],
		// 後述:エラーケース③ 対応
		"globals": {
			"ts-jest": {
				"tsConfig": "tsconfig.json"
			}
		}
	}
}
```

``` json:tsconfig.json
{
  // 後述:エラーケース③ 対応
  "compilerOptions": {
    "types": [
        "jest"
      ]
  },
}
```
### 3. テストコードサンプルを動かしてみる
引用元: [Facebook製のJavaScriptテストツール「Jest」の逆引き使用例](https://qiita.com/chimame/items/e97883fd46b67529d59f)
こちらに記載されていたサンプルコードをそのまま使わせて頂きました。

``` tsx:counter.tsx
export default class Counter {
  count = 0
  constructor(count) {
    this.count = count
  }

  increment() {
    this.count += 1
  }

  decrement() {
    this.count -= 1
  }
}
```

``` tsx:counter.test.tsx
import Counter from './counter'

describe('Counter', () => {
  describe('increment()', () => {
    test('increment', () => {
      const counter = new Counter(1)
      counter.increment()
      expect(counter.count).toBe(2)
    })
  })
})
```
準備が整ったところで`yarn test`をドン。

``` console:console
yarn test
yarn run v1.16.0
$ jest
 PASS  test/counter.test.tsx
  Counter
    increment()
      ✓ increment (2ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.482s
Ran all test suites.
✨  Done in 2.05s.
```
`package.json`で`**/test/**/*.test.tsx`となるファイルのみテストするようにしているので、`counter.test.tsx`のみがテスト対象とされる。

## 起きたエラー達
### エラーケース①: Couldn't find preset "module:metro-react-native-babel-preset" relative to directory...

``` console:console
$ yarn test
yarn run v1.16.0
$ jest
 FAIL  scripts/test.js
  ● Test suite failed to run

    Couldn't find preset "module:metro-react-native-babel-preset" relative to directory (ルートフォルダ)

      at node_modules/babel-core/lib/transformation/file/options/option-manager.js:293:19
          at Array.map (<anonymous>)
      at OptionManager.resolvePresets (node_modules/babel-core/lib/transformation/file/options/option-manager.js:275:20)
      at OptionManager.mergePresets (node_modules/babel-core/lib/transformation/file/options/option-manager.js:264:10)
      at OptionManager.mergeOptions (node_modules/babel-core/lib/transformation/file/options/option-manager.js:249:14)
      at OptionManager.init (node_modules/babel-core/lib/transformation/file/options/option-manager.js:368:12)
      at File.initOptions (node_modules/babel-core/lib/transformation/file/index.js:212:65)
      at new File (node_modules/babel-core/lib/transformation/file/index.js:135:24)
      at Pipeline.transform (node_modules/babel-core/lib/transformation/pipeline.js:46:16)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.513s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
`yarn test`実行時に、`module:metro-react-native-babel-preset`が`preset`にないよと怒られる。
→ babel側で対応するパターンとtypescript側で対応するパターンがあるが、前者の場合型チェックが出来なくなるらしいのでtypescript側で対応する。
※`.babelrc`や`babel.config.js`で対応する手段は前述のリンクを参照してください。

`package.json`に`transform`の設定を追加したらこちらは解決。ついでに自動生成の`script/test.js`までテスト対象に含めてしまって落ちていたので`testMatch`を記載した。

``` json:package.json
{
    // 前略
    "jest": {
        "preset": "react-native",
        "transform": {
            "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
        },
        "testMatch": [
            "**/test/**/*.test.tsx"
        ]
    }
}
```
※ コピペしたらスペースがずれていたらしく、落ちる;;;;;;ってしたのでFormatterはちゃんと使います(戒め)

### エラーケース②: SyntaxError: Unexpected identifier
tsxのテストケースサンプルを投入した後、そのままではどうやら読み込まれず。

``` console:console
$ yarn test
yarn run v1.16.0
$ jest
 FAIL  test/counter.test.tsx
  ● Test suite failed to run

    (ルートフォルダ)/test/counter.test.tsx:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import Counter from './counter'
                                                                                                    ^^^^^^^

    SyntaxError: Unexpected identifier

      at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:316:17)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.686s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
これも、`package.json`の`transform`問題。

``` json:package.json
{
    "jest": {
        "preset": "react-native",
        "transform": {
            "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
            "^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
        },
        "testMatch": [
            "**/test/**/*.test.tsx"
        ],
        "moduleFileExtensions": [
            "tsx",
            "js"
        ],
        "globals": {
            "ts-jest": {
                "tsConfig": "tsconfig.json"
            }
        }
    }
}
```

`"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"`と`moduleFileExtensions`設定をしたら解消してテストが通るように。

### エラーケース③: Cannot find name 'describe'.
テストソースで利用している`Jest`の関数(ここでは`describe`)がts-lintに引っ掛かる。

``` tsx:counter.test.tsx
import Counter from './counter'

describe('Counter', () => {
  describe('increment()', () => {
    test('increment', () => {
      const counter = new Counter(1)
      counter.increment()
      expect(counter.count).toBe(2)
    })
  })
})
```
エラー内容。`@types.jest`がないよと言われる。（入っている）

```
Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.ts(2582)
```
`tsconfig.json`の`compilerOptions.types`に`jest`を加えることで読み込まれるようになる。

``` json:tsconfig.json
{
  "compilerOptions": {
    "types": [
        "jest"
      ]
  },
}
```

## おわりに
ようやく大前提が終わったので気が重いのですが、本番たるスナップショットテスト(?????)に掛かろうと思います。
ちょっと見たけど意味がわからない……！！！！ 次の記事はテスト本番になると思われます。頑張ろう……。