Jestを使ったテスト高速化のため、ts-jestではなく、@swc/jestを使ってみたら予想以上に速かった話

## はじめに

TypeScriptでJestを使ってテストをする時、テストの実行速度が遅いことに不満を感じていました。

なんとなくTypeScript→JavaScriptへのビルドに時間がかかっているのだろうなと想像はついていたので、最近は `es-build` とかもあるため、テストの実行速度を上げることはできるのではないか？と考えました。

## テストの実行速度を上げる方法を調べてみる

ありました。

TypeScript→JavaScriptへのビルドに`ts-jest`を使うのではなく、 `esbuild-jest` を使う方法です。

https://github.com/aelbore/esbuild-jest

ですが、 `esbuild-jest` はPRの多くがマージされずに開発が途中で停止している様子です。（2022/09 時点）

これは困ったな。。。と、他の方法はないか調べてみると。。。。ありました。

今度は、`ts-jest`を使うのではなく、 `@swc/jest` を使う方法です。

https://github.com/swc-project/jest

SWCは、Speedy Web Compilerの略で、Rustで作られた次世代のコンパイラだそうです。

`swc-project`があり、その1つに `jest` が含まれている感じですね。

SWCは実績もあり、Next.js、Parcel、Deno などのツールや、Vercel、ByteDance、Tencent、Shopify などの企業で使用されているとのこと。

これは期待できる。。。ということで、早速使ってみることにしました。

## `@swc/jest` をインストール、Configファイルを用意する

とりあえずインストール。

```bash
yarn add -dev jest @types/jest @swc/core @swc/jest
```

`jest.config.json` では、向き先を `ts-jest` ではなく、 `@swc/jest` に。。。

- jest.config.json

```json
{
  "transform": {
    "^.+\\.(t|j)sx?$": ["@swc/jest"]
  },
  "testEnvironment": "node"
}
```

`@swc/jest` は、別途 `.swcrc` というConfigファイルを作ります。

- .swcrc

```json
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "dynamicImport": false,
      "decorators": false
    },
    "target": "es2022",
    "loose": false,
    "externalHelpers": false
  },
  "minify": false
}
```

各々の設定内容は以下に記載してあります。

https://swc.rs/docs/configuration/compilation

`.swcrc` を作って、 `target` を指定していることから、 `tsconfig.json` の内容は見ていないのでしょうね。

`tsconfig.json` を見ていないことで、何か大きな問題があるかというと。。。。微妙な気がします。

大抵は困らない気がする。。。

さておき、動かしてみましょう。

## テストする

- AWS S3のBucketからファイルを取得するコードのサンプル。

```tsx
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';

export async function main() {
  try {
    const s3 = new S3Client({ region: 'ap-northeast-1' });
    const obj = await s3.send(
      new GetObjectCommand({
        Bucket: 'dummyBucket',
        Key: 'dummyKey',
      })
    );
    if (
      obj.$metadata.httpStatusCode !== undefined &&
      obj.$metadata.httpStatusCode >= 400
    ) {
      return 'NG';
    }
    return 'OK';
  } catch (error) {
    return error as Error;
  }
}
```

- 上記コードのサンプルのテストコード。

```tsx
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { mockClient } from 'aws-sdk-client-mock';
import * as fs from 'fs';
import * as path from 'path';
import { main } from './main';

// main.tsのテスト
const s3Mock = mockClient(S3Client);
beforeEach(() => {
  s3Mock.reset();
});

describe('S3のオブジェクトを取得できる', () => {
  it('成功', async () => {
    s3Mock
      .on(GetObjectCommand, {
        Bucket: 'dummyBucket',
        Key: 'dummyKey',
      })
      .resolves({
        $metadata: {
          httpStatusCode: 200,
        },
        Body: fs.createReadStream(
          path.resolve(__dirname, './main.test.s3read.txt')
        ),
      });

    expect(await main()).toBe('OK');
  });

  it('失敗', async () => {
    s3Mock
      .on(GetObjectCommand, {
        Bucket: 'dummyBucket',
        Key: 'dummyKey',
      })
      .resolves({
        $metadata: {
          httpStatusCode: 400,
        },
        Body: fs.createReadStream(
          path.resolve(__dirname, './main.test.s3read.txt')
        ),
      });

    expect(await main()).toBe('NG');
  });
});

describe('S3のオブジェクトを取得できない', () => {
  it('成功', async () => {
    const error = new Error('S3のオブジェクトを取得できない');

    s3Mock
      .on(GetObjectCommand, {
        Bucket: 'dummyBucket',
        Key: 'dummyKey',
      })
      .rejects(error);

    expect(await main()).toEqual(error);
  });
});
```

- テスト実行結果。

```sh
## @swc/jest
$ jest --coverage
 PASS  src/main.spec.ts
  S3のオブジェクトを取得できる
    ✓ 成功 (31 ms)
    ✓ 失敗 (3 ms)
  S3のオブジェクトを取得できない
    ✓ 成功 (3 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 main.ts  |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.324 s
Ran all test suites.
✨  Done in 2.53s.

## ts-jest
$ jest --coverage
 PASS  src/main.spec.ts (5.189 s)
  S3のオブジェクトを取得できる
    ✓ 成功 (28 ms)
    ✓ 失敗 (4 ms)
  S3のオブジェクトを取得できない
    ✓ 成功 (3 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 main.ts  |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        5.498 s
Ran all test suites.
✨  Done in 6.71s.

```

## 結果

`@swc/jest` は、「2.53s」。 `ts-jest` は、「6.71s」という結果になりました。

`ts-jest`と比べて、倍以上早くなりましたね。

`@swc/jest`を使って困ることがあるかどうか現段階では見えていませんが、まずは使ってみて様子を見てみようと思います。