Jest + TypeScriptで覚えておきたいParametersとReturnType&小技集

Jest + TypeScript でテストコードを書く際に、覚えておくととても便利だと感じたものを備忘録的に記事にいたします。

# 1. Parameters<Type>
https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype

まず、TypeScriptの `Utility Types` である `Parameters<Type>` です。
(Utility Typesとは、TypeScriptが標準で提供している便利な関数のような機能です)

## 特徴
- 関数に対して使用する
- `Parameters<関数型>` とすることで、**関数の引数をタプル型** にしてくれる
- `Parameters<typeof someFun>` でもOK

## コード例
では、例を見てみましょう。

```typescript
// 例：1
type T0 = Parameters<() => string>; // type T0 = [] || 引数がないので空タプル

// 例：2
type T1 = Parameters<(s: string) => void>; // type T1 = [s: string] || 引数がタプル型になっている

// 例：3
const someFunc = (arg: { a: number; b: string }) => void {}
type T3 = Parameters<typeof someFunc>;
/**
type T3 = [arg: {
    a: number;
    b: string;
}]
*/
```


# 2. ReturnType<Type>
https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype

続いては、同じく `Utility Types` である `ReturnType<Type>` です。

## 特徴
- 関数に対して使用する
- `ReturnType<関数型>` とすることで、**関数の戻り値で構成される型** にしてくれる
- `ReturnType<typeof someFun>` でもOK

## コード例
では、例を見てみましょう。

```typescript
// 例：1
type T0 = ReturnType<() => string>; // type T0 = string || 戻り値がstringなので

// 例：2
type T1 = ReturnType<(s: string) => void>; // type T1 = void

// 例：3
const someFunc = (): { a: number; b: string } => ({a: 1, b: 'string'})
type T3 = ReturnType<typeof someFunc>;
/**
type T3 = {
    a: number;
    b: string;
}
*/
```

# 3. Jestで使ってみる
2つの `Utility Types` を紹介させて頂きました。

ところでこの2つはどんな時に便利なのでしょうか？

もちろん色々なケースが考えられますが、
**関数のテストコードを書くときにめちゃくちゃ便利** でした。

## TestDataの型にParametersとReturnTypeを使った例
例えば、以下のように状態を持たない純粋な関数があったとします。

```typescript:service/existsPath.ts
import isEmpty from 'lodash/isEmpty';

// args.pathに値が存在するかを確認する
export const existsPath = (args: { path: string; params: string }): boolean => {
  return !isEmpty(args.path);
};
```

この関数をJestを使ってテストコードを書く時に、以下のようにすると引数と戻り値の型をサクッと作れます。

```typescript:test/ExistsPath.spec.ts
import { existsPath } from '@/service/existsPath';

describe('existsPathのテスト', () => {
  type TestData = {
    params: Parameters<typeof existsPath>;
    expected: ReturnType<typeof existsPath>;
  };
});
```

### 何が嬉しいか？
この `type TestData` が作れることで何が嬉しいのでしょう？

そうです、テストデータを準備する時にも、しっかりと **型が効く** のです。

テストコードは以下のようにしてみます。

```typescript:test/ExistsPath.spec.ts
import { existsPath } from '@/service/existsPath';

describe('existsPathのテスト', () => {
  type TestData = {
    params: Parameters<typeof existsPath>;
    expected: ReturnType<typeof existsPath>;
  };

  const testData: TestData = {
    params: [{ path: 'ほげ', params: '' }],
    expected: true,
  };

  test('pathに値が存在していればtrueを返す', () => {
    expect(existsPath(testData.params[0])).toBe(testData.expected);
  });
});

```

このコードを記述している時もインテリセンスが効いてくれます。
![Screen Shot 2022-01-20 at 23.15.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399610/f90d0194-f2d6-3a62-edc0-bb8ff38f5cda.png)
テストを走らせてみます。

無事通りました。
![Screen Shot 2022-01-20 at 23.17.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399610/01fefb1f-0ccb-38b4-f9e7-fa50d97ca64a.png)

## ループさせてより便利にしてみる
ここまでだと、ただ型が効いただけになりますが、
更に便利にすることができます。

**testDataを配列** にして、`test.each` で **ループ** させてみましょう。

「testData」と「expect」の箇所を以下のように修正します。

```typescript:test/ExistsPath.spec.ts
const testData: TestData[] = [
  {
    params: [{ path: 'ほげ', params: '' }],
    expected: true,
  },
  {
    params: [{ path: '', params: '' }],
    expected: false,
  },
];

test.each(testData)('入力値と返却値が期待通りであること', (testData: TestData) => {
  expect(existsPath(...testData.params)).toBe(testData.expected);
});
```
テストを走らせてみます。
![Screen Shot 2022-01-20 at 23.30.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399610/eafa8f59-e087-2e14-c97a-15333ccd6036.png)
無事通りました。

最終的なコードは以下のようになります。

```typescript:test/ExistsPath.spec.ts
import { existsPath } from '@/service/existsPath';

describe('existsPathのテスト', () => {
  type TestData = {
    params: Parameters<typeof existsPath>;
    expected: ReturnType<typeof existsPath>;
  };

  const testData: TestData[] = [
    {
      params: [{ path: 'ほげ', params: '' }],
      expected: true,
    },
    {
      params: [{ path: '', params: '' }],
      expected: false,
    },
  ];

  test.each(testData)('入力値と返却値が期待通りであること', (testData: TestData) => {
    expect(existsPath(...testData.params)).toBe(testData.expected);
  });
});
```

このように「ParametersとReturnType」を使うことにより、
型の情報を得ながらテストを効率的に書くことができました。

より厳密に記述すると、params も 戻り値も interface を別途切る方が綺麗かもしれませんが、
サクッと効率良くテストコードを記述するにはすごく便利だと感じました。

# まとめ
- Parameters と ReturnType は Utility Types である
- Utility Types とは、TypeScript が標準で提供している便利な関数のような機能
- Jest + TypeScript では Parameters と ReturnType を使うことで型の情報を得ながら、効率的に記述できた
- 最後の最後だけど、別にJestだけじゃなくて色々なケースで使えるよ

以上となります。
お読み頂き有難うございました。