jestでテスト

##jestとは
Node.jsをテストするためのライブラリ
＜公式ドキュメント＞  https://jestjs.io/docs/en/getting-started

※Node.jsのテストアプリとしては、他にもmocha(https://mochajs.org/ )が有名。

##テストコードが必要な理由
・開発の時間短縮。コードを変更した後でも、テストコマンドを打つだけで自動的にテストしてくれる
・より信頼性の高い（バグの少ない）コーディングができる
・リファクタリングや機能の追加・削除が簡単にできる

##アプリをテストするための準備
●jestのインストール

```
npm i jest
```
※npmでのjestのページ ( https://www.npmjs.com/package/jest )

●テスト用の環境を設定する
※本番用の環境がすでに「dev.env」にまとめられている場合

・ファイル構成

```
└── App
    ├── config           //環境を保存するフォルダ
    |      ├── dev.env  //本番用環境のための環境変数を格納しているファイル  
    |      └── test.env //テスト環境のための環境変数を格納しているファイル  
```

・test.envの設定

```json
PORT=3010
MONGODB_URL=mongodb://127.0.0.1:27017/task-manager-api-test
```
※テスト用のデータベースと接続させるようにMongoDBの設定を記述する

●package.jsonの設定

```json
"scripts": {
    "start": "node src/index.js",
    "dev": "env-cmd -f ./config/dev.env nodemon src/index.js",
    "test": "env-cmd -f ./config/test.env jest --watch"　　　//test時に動かす環境を記述。
  },
  "jest": {
    "testEnvironment": "node"  //jestで nodeのコードをテストすると記述
  },
```
※「scripts」に「test」を記述することで、ターミナルに「npm test」でテスト（ファイル名にtestと含まれているもの）を動かすようになる。

※「test」に「--watch」で、「npm test」後に、ターミナルでjestが開きっぱなしになる

##expressをテストするためのnpmパッケージ
「super test」 https://www.npmjs.com/package/supertest

・テストコード

```javascript
const app =require('./app')　　　//portをlistenする以外のアプリの処理を読み込み

request(app)
       .expect(...)   //アプリが立ち上がっていなくてもexptectでテストできる
```

→super testパッケージを用いると、アプリが立ち上がっていなくてもテストを行うことができる




##通常のテスト

・testのためのファイル構成

```
└── App
    ├── test  //テストを保存するフォルダ
    |      └── fixture //テスト環境のための環境変数を格納しているファイル 
　　 |　　　　　 ├── db.js  //テスト用データをデータベースに格納するためのファイル
    |          └── async.test.js //テストコードを記述するファイル
```

●通常のテスト
・first.test.js

```javascript
test('テストケース名',() => { テストしたい関数 })　//正常なら処理完了するテスト

test('テストケース名', ()=> { 
  throw new Error ('エラー') 　　//正常ならエラーを返したいテスト項目
})
```
jestでは通常、上記の書き方でテストコードを書く。
「npm test」でテスト開始。ファイル名に「test」が含まれているテストファイルが実行される。


##非同期処理のテスト
・async.test.js

```javascript
test('Async test demo', (done) => {　　//引数にdoneを設定
  setTimeout(() => {
    expect(1).toBe(2)
    done()　//非同期処理（ここではsetTimeout）が終わるタイミングでdoneを呼び出す
  }, 2000)  
})
```
↓
今回のテストでは、「setTimeout」をすると「1」が「2」になるかテストしている
↓
２秒後にエラーが返る。

※「toBe」について：　[jest公式ドキュメント](https://jestjs.io/docs/en/expect#tobevalue)

非同期処理のテストは、非同期処理が終わった時点で引数doneを呼び出すように設定しないと、ちゃんとテストされない（非同期処理を待たずにテストをパスしてしまう）

●Promiseを使うコードのテスト

```javascript
const add = (a, b) => {
  return new Promise ((resolve, reject) => {
    setTimeout(() => {
        if(a < 0 || b < 0){
          return reject('Number must be non-negative')
        }
      resolve(a + b)
    }, 2000)
  })
}
```

上記のコードをテストする場合
・promise.test.js

```javascript

test('addは機能しているか', (done)=> {
  add(2, 3).then((sum) => {　//thenを使ってチェインさせる
    expect(sum).toBe(5)
    done()
  })
})
```

async/awaitを使ってテストする場合

```javascript
test('addは機能しているか、async/awaitで', async() => {
  const sum = await add(11, 22)
  expect(sum).toBe(33)
})

```
##テストの前に行う処理・テストの後に行う処理を記述する
[「Setup and Teardown」](https://jestjs.io/docs/en/setup-teardown)を用いる。

> beforeEach(() => {
  initializeCityDatabase();
});

> afterEach(() => {
  clearCityDatabase();
});

・beforeEach：テストの前に行う処理を記述する

・afterEach：テストの後に行う処理を記述する

例）テストの前に、テスト用データベース内の全ての項目を消す
・users.test.js

```javascript
const User = require('../src/models/user')

const userOne = {  //テストデータ用のデータベースの中身
  neme:'aaa',
  password: '12345'
}

beforeEach(async() => {　　　//beforeEachは非同期処理
  await User.deleteMany()　　//データベースの中身を削除
  await new User(userOne).save()  //空になったデータをデータベースに保存
})

```

##ライブラリのmockを作成する
・jest公式ドキュメント
[Mocking Node Modules](https://jestjs.io/docs/en/manual-mocks#mocking-node-modules )を参照

・mockライブラリを作成する場合のファイル構成

```
└── App
    ├── test  //テストを保存するフォルダ
    |      └── fixture //テスト環境のための環境変数を格納しているファイル 
　　 |　　　 |　 ├── db.js  //テスト用データをデータベースに格納するためのファイル
    |      |   └── test.js//テストコードを記述するファイル
　　　　　　 └── __mocks__  //モックファイルを記述
               └── sendgrid //モックを作成するAPI名
　　　　　　　　　　　　　└── test.js
```

・test.js

```javascript

module.exports = {
  send(){
    
  }
}
```
↓
今回はsendgridという、メール送信のためのライブラリ（https://sendgrid.com/docs/api-reference/ ）のモックを作成している。

コード中に「sgMail.send()」でメール送信をする箇所があった場合、テスト環境では作成したモックのsendgridが実行されるため、「send()」の処理は上記のファイルに書かれた通りのもの（今回の場合は何もしないこと）になる。