🗂

自分の作ったChrome拡張にテストを組み込む(2) テスト追加編

2022/09/01に公開

JestとReact-Testing-Libraryを使って単体テストをやりたい

https://zenn.dev/satoshie/articles/eaa33eea937091

このメモの続きです。

対象レポジトリ

https://github.com/satoshi-nishinaka/chrome-extension-study

テストケース用のディレクトリを追加する

__tests__ というディレクトリ内にテスト用のファイルを追加していく。

現在のディレクトリ
$ tree -d src
src
├── Component
├── Container
├── FunctionalButton
├── Functions
├── Section
│   └── ShortCutLinks
└── scss

※ディレクトリ構成が気持ち悪いのは許してください…

関数を切り出しているのはFunctionsディレクトリなのでそこにテストケース用のディレクトリを追加する。

ディレクトリを追加
$ mkdir -p src/Functions/__tests__

テストケースの作成

配列の要素をユニークにする関数を置いているので、期待した動きをするかどうかのテストケースを書いていく。

参考: JavaScriptのArrayでuniqする8つの方法(と、その中で最速の方法) - Qiita

その前に、Jestのドキュメントを見ながら書き方を確認。
参考: Getting Started · Jest

sum.test.js
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

なるほど。 test('説明', () => expect(関数(引数)).toBe(期待する値)); みたいな構文なのね?というのがわかる。

早速書いてみる。

src/Functions/__tests__/Unique.ts
import { unique } from '../Unique';

test('remove duplicate element', () => {
  const input = ['A', 'B', 'AA', 'B'];
  const expected = ['A', 'B', 'AA'];
  expect(unique(input)).toBe(expected);
});

テストしてみる

テストケースを書いたので早速テストしてみる。

テスト実行
$ npm run test

> short-cut-extension@1.3.2 test
> npx jest

 FAIL  src/Functions/__tests__/Unique.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /Users/satoshie/git/chrome-extension-study/src/Functions/__tests__/Unique.ts:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { unique } from '../Unique';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1796:14)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.562 s
Ran all test suites.

ダメじゃん!!

jest.config.tsの修正

TypeScriptのファイルはちゃんとJestの設定で変換の指定を指定してあげないといけない。
jest.config.ts に transform の指定を追加する。

 const config: Config = {
   verbose: true,
+  transform: {
+    '^.+\\.ts$': 'ts-jest',
+  },
 };

追加した状態で再度テストを実行してみる。

テスト実行
$ npm run test

> short-cut-extension@1.3.2 test
> npx jest

 FAIL  src/Functions/__tests__/Unique.ts
  ● Test suite failed to run

    Unable to process '/Users/satoshie/git/chrome-extension-study/src/Functions/__tests__/Unique.ts', please make sure that `outDir` in your tsconfig is neither `''` or `'.'`. You can also configure Jest config option `transformIgnorePatterns` to inform `ts-jest` to transform /Users/satoshie/git/chrome-extension-study/src/Functions/__tests__/Unique.ts

      at TsCompiler.getCompiledOutput (node_modules/ts-jest/dist/legacy/compiler/ts-compiler.js:165:27)
      at TsJestCompiler.getCompiledOutput (node_modules/ts-jest/dist/legacy/compiler/ts-jest-compiler.js:13:39)
      at TsJestTransformer.processWithTs (node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:232:37)
      at TsJestTransformer.process (node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:161:24)
      at ScriptTransformer.transformSource (node_modules/@jest/transform/build/ScriptTransformer.js:619:31)
      at ScriptTransformer._transformAndBuildScript (node_modules/@jest/transform/build/ScriptTransformer.js:765:40)
      at ScriptTransformer.transform (node_modules/@jest/transform/build/ScriptTransformer.js:822:19)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.585 s
Ran all test suites.

エラーの内容が変わった。

please make sure that outDir in your tsconfig is neither '' or '.'.

tsconfig内の outDir の設定が '''.' になっていないか確認してください。とのことだけど、 outDir の設定はそうはなっていないのでここではなさそう。
トランスパイル周りな気がするんだよなぁ。

そう言えばテストにばかり注意を向けていたけど、本体のビルドは影響ないのか?と思っていざビルドしてみると…

$ npm run build
(省略)
ERROR in /Users/satoshie/git/chrome-extension-study/tsconfig.json
/Users/satoshie/git/chrome-extension-study/tsconfig.json
[tsl] ERROR
      TS6059: File '/Users/satoshie/git/chrome-extension-study/jest.config.ts' is not under 'rootDir' '/Users/satoshie/git/chrome-extension-study/src'. 'rootDir' is expected to contain all source files.
  The file is in the program because:
    Root file specified for compilation

1 error has detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.

jest.config.ts をルートディレクトリに置いてしまっていたせいで発生していたので src/ 配下に移動した。
移動したら既存のコードのビルドは通るようになった。

気を取り直してこの状態でテスト実行してみる。

テスト実行
$ npm run test

> short-cut-extension@1.3.2 test
> npx jest

 FAIL  src/Functions/__tests__/Unique.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /Users/satoshie/git/chrome-extension-study/src/Functions/__tests__/Unique.ts:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { unique } from '../Unique';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1796:14)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.541 s
Ran all test suites.

振り出しに戻ってる…。

公式ドキュメント見ると --config で設定ファイルを指定してね!って書いてあったので package.jsontest の設定を修正。

package.json
"test": "npx jest --config src/jest.config.ts",

そして再度テスト実行してみる。

テスト実行
$ npm run test

> short-cut-extension@1.3.2 test
> npx jest --config src/jest.config.ts

 FAIL  src/Functions/__tests__/Unique.ts
  ✕ remove duplicate element (5 ms)

  ● remove duplicate element

    expect(received).toBe(expected) // Object.is equality

    If it should pass with deep equality, replace "toBe" with "toStrictEqual"

    Expected: ["A", "B", "AA"]
    Received: serializes to the same string

      5 |   const expected = ['A', 'B', 'AA'];
      6 |   expect(unique(input)).toBe(expected);
    > 7 | });
        |              ^
      8 |

      at Object.<anonymous> (Functions/__tests__/Unique.ts:7:41)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.772 s
Ran all test suites.

おおおおおお!ちゃんとテスト出来てる!
これは異なるオブジェクト同士をEqualsで比較しようとしたからFailedになったんだね!
これはテストケースが正しくないから書き換える。

Expect · Jest: toStrictEqual

Use .toStrictEqual to test that objects have the same types as well as structure.

Differences from .toEqual:

Keys with undefined properties are checked. e.g. {a: undefined, b: 2} does not match {b: 2} when using .toStrictEqual.
Array sparseness is checked. e.g. [, 1] does not match [undefined, 1] when using .toStrictEqual.
Object types are checked to be equal. e.g. A class instance with fields a and b will not equal a literal object with fields a and b.

今度こそ!

早速テストコードを書き換えてみる。

src/Functions/__tests__/Unique.ts
import { unique } from '../Unique';

test('remove duplicate element', () => {
  const input = ['A', 'B', 'AA', 'B'];
  const expected = ['A', 'B', 'AA'];
  expect(unique(input)).toStrictEqual(expected);
});
テスト実行
$ npm run test

> short-cut-extension@1.3.2 test
> npx jest --config src/jest.config.ts

 PASS  src/Functions/__tests__/Unique.ts
  ✓ remove duplicate element (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.754 s, estimated 2 s
Ran all test suites.

無事テストも通るようになりました 🥰

次は react-testing-library を導入していきます!
https://zenn.dev/satoshie/articles/82a55fc935d0db

Discussion