【Jest】テストコードの書き方

Jestを使ってテストコードを書いたのでまた書けるようにアウトプットします。

## 環境
TypeScript
React
Next.js
Jest　←テスト用ライブラリ

Jestは事前にインストールしておきます。

## __test__フォルダ作成とデータ用意

私の場合はバックエンドの特定の関数をテストの対象としていたので、特に一部の機能をテスト用に切り出すようなことはしませんでしたが、テストしたい箇所を関数に切り出す作業が必要なこともあるようです。

結構引数の多い関数だったのとテストしたいパターンもそこそこあったので、テストデータは別ファイルで定義してdataフォルダにまとめておきました。

テストしたい関数は引数が6つ必要になるので引数を用意するのですが、最初テストコードに直接ベタベタと書いていたらごちゃごちゃしてきて途中で引数は別ファイルに定義することとしました。


テスト対象の関数の最初の部分
```ts
export const calculate = (
  practicing: Growth[],
  acquisition: Growth[],
  walking: Growth[],
  wakeWindows: WakeWindows[],
  baby: Baby,
  sleepingSituation: SleepingSituation[]
) => {
 //処理内容
```
この引数を用意していきます！


赤ちゃんの情報定義したファイル
```ts
import { Baby } from "@prisma/client";
interface BabyCollection {
  newbornBaby: Baby;
  oneMonthBaby: Baby;
  twoMonthBaby: Baby;
  fiveMonthBaby: Baby;
  sixMonthBaby: Baby;
  eightMonthBaby: Baby;
  tenMonthBaby: Baby;
  eightteenMonthBaby: Baby;
}
export const baby: BabyCollection = {
  newbornBaby: {
    id: 9,
    name: "新生児",
    birthday: new Date("2024-05-03"),
    birthWeight: 3000,
    expectedDateOfBirth: new Date("2024-05-03"),
    gender: "BOY",
    created: new Date("2024-05-23"),
    updated: new Date("2024-05-24"),
    milestone: [],
  },
  oneMonthBaby: {
    id: 9,
    name: "1ヶ月ベビー",
    birthday: new Date("2024-04-03"),
    birthWeight: 3000,
    expectedDateOfBirth: new Date("2024-04-03"),
    gender: "BOY",
    created: new Date("2024-05-23"),
    updated: new Date("2024-05-24"),
    milestone: [],
  },
 　・・・
```

上記のような感じで月齢ごとに18か月まで用意しました。

次に発達のデータを定義します。

```ts
//型は省略
export const practicing: practicingCollection = {
  fiveMonth: [
    {
      babyId: 9,
      id: 1,
      milestone: "TURNING_OVER_AND_OVER",
      startedAt: new Date("2024-05-24"),
      archevedAt: null,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-24"),
    },
  ],

  sixMonth: [
    {
      babyId: 9,
      id: 1,
      milestone: "TURNING_OVER_AND_OVER",
      startedAt: new Date("2024-05-24"),
      archevedAt: null,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-24"),
    },
  ],
　　・・・
```

こんな感じで月齢ごとに用意しました。
低月齢は動かないので発達は計算の対象から外していて5か月スタートにしています。

そして、直近の睡眠時間影響するので睡眠時間のデータです。

```ts
import { SleepingSituation } from "@prisma/client";
interface lengthType {
  short: SleepingSituation[];
  long: SleepingSituation[];
}
interface timeType {
  earlymorning: lengthType;
  morning: lengthType;
  noon: lengthType;
  evening: lengthType;
  night: lengthType;
}
interface sleepingSituationCollection {
  lowLunarAgeWakeWindows: timeType;
  sixMonthWakeWindows: timeType;
  eightMonthWakeWindows: timeType;
  tenMonthWakeWindows: timeType;
  eighteenMonthWakeWindows: timeType;
  addition_eighteenMonthWakeWindows: {
    noon: SleepingSituation[];
    evening: SleepingSituation[];
  };
}
const long = {
  earlymorning: {
    babyId: 9,
    bedTime: null,
    sleep: new Date("2024-05-20T01:00:00"),
    wakeup: new Date("2024-05-20T04:00:00"),
    id: 1,
    createUser: 20,
    changeUser: 20,
    created: new Date("2024-05-20"),
    updated: new Date("2024-05-20"),
  },
  morning: {
    babyId: 9,
    bedTime: null,
    sleep: new Date("2024-05-20T04:00:00"),
    wakeup: new Date("2024-05-20T07:00:00"),
    id: 1,
    createUser: 20,
    changeUser: 20,
    created: new Date("2024-05-20"),
    updated: new Date("2024-05-20"),
  },
  noon: {
    babyId: 9,
    bedTime: null,
    sleep: new Date("2024-05-20T07:00:00"),
    wakeup: new Date("2024-05-20T10:00:00"),
    id: 1,
    createUser: 20,
    changeUser: 20,
    created: new Date("2024-05-20"),
    updated: new Date("2024-05-20"),
  },
  evening: {
    babyId: 9,
    bedTime: null,
    sleep: new Date("2024-05-20T12:00:00"),
    wakeup: new Date("2024-05-20T15:00:00"),
    id: 1,
    createUser: 20,
    changeUser: 20,
    created: new Date("2024-05-20"),
    updated: new Date("2024-05-20"),
  },
  night: {
    babyId: 9,
    bedTime: null,
    sleep: new Date("2024-05-20T20:00:00"),
    wakeup: new Date("2024-05-20T23:00:00"),
    id: 1,
    createUser: 20,
    changeUser: 20,
    created: new Date("2024-05-20"),
    updated: new Date("2024-05-20"),
  },
};
export const sleepingSituation: sleepingSituationCollection = {
  lowLunarAgeWakeWindows: {
    earlymorning: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T03:30:00"),
          wakeup: new Date("2024-05-20T04:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.earlymorning],
    },
    morning: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T06:30:00"),
          wakeup: new Date("2024-05-20T07:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.morning],
    },
    noon: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T09:30:00"),
          wakeup: new Date("2024-05-20T10:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.noon],
    },
    evening: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T14:30:00"),
          wakeup: new Date("2024-05-20T15:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.evening],
    },
    night: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T22:30:00"),
          wakeup: new Date("2024-05-20T23:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.night],
    },
  },
  //6ヶ月まで1サイクル50分だから短いのは49分以内
  sixMonthWakeWindows: {
    earlymorning: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T03:11:00"),
          wakeup: new Date("2024-05-20T04:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.earlymorning],
    },
    morning: {
      short: [
        {
          babyId: 9,
          bedTime: null,
          sleep: new Date("2024-05-20T05:11:00"),
          wakeup: new Date("2024-05-20T06:00:00"),
          id: 1,
          createUser: 20,
          changeUser: 20,
          created: new Date("2024-05-20"),
          updated: new Date("2024-05-20"),
        },
      ],
      long: [long.morning],
    },
    ・・・
```

時間帯と長さと月齢の複数パターン用意しました。

最後に活動時間です。

```ts
import { WakeWindows } from "@prisma/client";
interface WakeWindowsCollection {
  newbornWakeWindows: WakeWindows[];
  oneMonthWakeWindows: WakeWindows[];
  twoMonthWakeWindows: WakeWindows[];
  fiveMonthWakeWindows: WakeWindows[];
  sixMonthWakeWindows: { hourly: WakeWindows[]; basicOnly: WakeWindows[] };
  eightMonthWakeWindows: WakeWindows[];
  tenMonthWakeWindows: WakeWindows[];
  eightteenMonthWakeWindows: {
    hourly: WakeWindows[];
    basicOnly: WakeWindows[];
  };
}

export const wakeWindows: WakeWindowsCollection = {
  newbornWakeWindows: [
    {
      babyId: 9,
      id: 1,
      type: "ALL",
      time: 40,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
  ],
  oneMonthWakeWindows: [
    {
      babyId: 9,
      id: 1,
      type: "ALL",
      time: 50,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
  ],
  twoMonthWakeWindows: [
    {
      babyId: 9,
      id: 1,
      type: "ALL",
      time: 60,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
  ],
  fiveMonthWakeWindows: [
    {
      babyId: 9,
      id: 1,
      type: "ALL",
      time: 70,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
    {
      babyId: 9,
      id: 2,
      type: "MORNING",
      time: 70,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
    {
      babyId: 9,
      id: 3,
      type: "NOON",
      time: 80,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
    {
      babyId: 9,
      id: 4,
      type: "EVENING",
      time: 85,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
  ],
  sixMonthWakeWindows: {
    hourly: [
      {
        babyId: 9,
        id: 1,
        type: "ALL",
        time: 80,
        createUser: 20,
        changeUser: 20,
        created: new Date("2024-05-03"),
        updated: new Date("2024-05-03"),
      },
      {
        babyId: 9,
        id: 2,
        type: "MORNING",
        time: 75,
        createUser: 20,
        changeUser: 20,
        created: new Date("2024-05-03"),
        updated: new Date("2024-05-03"),
      },
      {
        babyId: 9,
        id: 3,
        type: "NOON",
        time: 80,
        createUser: 20,
        changeUser: 20,
        created: new Date("2024-05-03"),
        updated: new Date("2024-05-03"),
      },
      {
        babyId: 9,
        id: 4,
        type: "EVENING",
        time: 90,
        createUser: 20,
        changeUser: 20,
        created: new Date("2024-05-03"),
        updated: new Date("2024-05-03"),
      },
    ],
    basicOnly: [
      {
        babyId: 9,
        id: 1,
        type: "ALL",
        time: 80,
        createUser: 20,
        changeUser: 20,
        created: new Date("2024-05-03"),
        updated: new Date("2024-05-03"),
      },
    ],
  },
  eightMonthWakeWindows: [
    {
      babyId: 9,
      id: 1,
      type: "ALL",
      time: 80,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
    {
      babyId: 9,
      id: 2,
      type: "MORNING",
      time: 85,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
    {
      babyId: 9,
      id: 3,
      type: "NOON",
      time: 90,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
    {
      babyId: 9,
      id: 4,
      type: "EVENING",
      time: 100,
      createUser: 20,
      changeUser: 20,
      created: new Date("2024-05-03"),
      updated: new Date("2024-05-03"),
    },
  ],

  ```

ここまで出来たらやっと準備完了でテストコードの記述です。
最初にテストをするのに必要なdescribe、定義したデータファイル、テスト対象の関数をimportします。

```ts
import { describe } from "node:test";
import { baby } from "./data/babies";
import { practicing, acquisition, walking } from "./data/growth";
import { sleepingSituation } from "./data/sleepingAituation";
import { wakeWindows } from "./data/wakeWindows";
import { calculate } from "@/app/api/dashboard/nextSleepTime/_utils/calculate";
```

## テストコード本体の記述

```ts
describe("calculate", () => {
  describe("(1)新生児/活動時間40min/睡眠30min/4時起床", () => {
    it("テスト", async () => {
      const result = calculate(
        [],
        [],
        [],
        wakeWindows.newbornWakeWindows,
        baby.newbornBaby,
        sleepingSituation.lowLunarAgeWakeWindows.earlymorning.short
      );
      console.log(`(1)新生児/活動時間40min/睡眠30min/4時起床 : ${result}`);
    });
  });
  describe("(2)新生児/活動時間40min/睡眠3hours/4時起床", () => {
    it("テスト", async () => {
      const result = calculate(
        [],
        [],
        [],
        wakeWindows.newbornWakeWindows,
        baby.newbornBaby,
        sleepingSituation.lowLunarAgeWakeWindows.earlymorning.long
      );
      console.log(`(2)新生児/活動時間40min/睡眠3hours/4時起床 : ${result}`);
    });
  });
  describe("(3)新生児/活動時間40min/睡眠30min/7時起床", () => {
    it("テスト", async () => {
      const result = calculate(
        [],
        [],
        [],
        wakeWindows.newbornWakeWindows,
        baby.newbornBaby,
        sleepingSituation.lowLunarAgeWakeWindows.morning.short
      );
      console.log(`(3)新生児/活動時間40min/睡眠30min/7時起床 : ${result}`);
    });
  });
  ・・・
```

こんな感じでどんどん書いていきました。

describeはテストグループの定義で一つ目の引数はターミナルに出力され、第二引数の関数が実行されます。
describeの第二引数にはitを記述し、itの第一引数にはターミナルに出力され、第二引数に個々のテストケースを定義し、ここでテストしたい関数に引数を渡して記述していきます。

一つ目のテストケースを見ていくと
```ts
  describe("(1)新生児/活動時間40min/睡眠30min/4時起床", () => {
    it("テスト", async () => {
      const result = calculate(
        [],
        [],
        [],
        wakeWindows.newbornWakeWindows,
        baby.newbornBaby,
        sleepingSituation.lowLunarAgeWakeWindows.earlymorning.short
      );
      console.log(`(1)新生児/活動時間40min/睡眠30min/4時起床 : ${result}`);
    });
  });
```
単純にitの中で関数呼び出して結果を出力しているだけです。
これをテストしたいケースでひたすら書きます！！

テストコードのファイル名は「hoge.test.ts」です。
今回の私のテストコードは関数名から「calculate.test.ts」としています。

データ別でファイル分割していても750行くらい書いたのでクタクタでしたが、数千行になることは普通だそうで、恐ろしい世界だと思いました・・

## 実行
```
npm test
```
実行すると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816029/517c044b-5284-2753-203f-bdd2d5bfc546.png)


ターミナルにズラーーーっと結果が出力されます。

## テスト結果の管理
私はテストケースとテスト結果を記録しておきたかったので、スプレッドシートにまとめました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816029/a7336fdc-5952-2853-0a86-87e8085f1097.png)


## 感想
テストコードを書いたのは初めてでしたが、本当に地道な作業でした！
次回のおすすめねんね時刻を計算する箇所なのでアプリの要の機能で、ちゃんと意図する通りに算出されるかは超重要ですし、しっかりテストしておけば安心してリリースできると思いました！！