0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jestがエラー:Jest encountered an unexpected tokenを出したのでESM形式で起動させた【NestJS】

0
Posted at

はじめに

表題のとおり、NestJS上でJestを使ったテストを実施しようとした際、Jestが下記のエラーを出しました。

悪戦苦闘しながら、なんとか解決できたので忘備録として残しています。
もし何かご指摘ありましたら、ご教授お願いいたします!

  ● 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/hogehoge/Projects/practice/backend/src/course/course.controller.spec.ts:1
    import { Test } from '@nestjs/testing';
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

Jestがimportを認識できない、とのことなのでこれを解決していきます。

原因はCommonJS前提でJestが起動していたから

たとえpackage.jsonに「"type": "module"」と記述して、ECMAScript Modules (ESM)形式にしても、Jest側がCommonJS前提でテストしようとすると、衝突が起きてしまいます。

Jest「CommonJSだからrequireで読もう」
→ importが来る
→ ?????
→ エラー

設定ファイルを修正しJestをESM形式で起動する

まずは必要なパッケージであるjestやts-jestが入っているかを確認。
もしパッケージがなければインストールしましょう。

npm install-D jest ts-jest @types/jest

その上で、tsconfig.jsonとpackage.jsonを修正していきます。

tsconfig.jsonのモジュール形式をES Modulesに設定

tsconfig.jsonの"module"の項目を"nodenext"に変更します。

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext",
  }
}

なおCommonJS形式にしたい場合は
"module": "commonjs" → Node.jsの標準(require/module.exports)

package.jsonのJest項目を設定

package.jsonの中身を一部抜粋して表示しています。

{
  "type": "module",
  "jest": {
    "preset": "ts-jest/presets/default-esm",
    "extensionsToTreatAsEsm": [".ts"],
    "moduleNameMapper": {
      "^(\\.{1,2}/.*)\\.js$": "$1"
    },
    "moduleFileExtensions": ["js", "json", "ts"],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "collectCoverageFrom": ["**/*.(t|j)s"],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node",
    "setupFiles": [
      "<rootDir>/../test/jest-env-setup.ts"
    ]
  }
}

特に下記の部分が重要で

    "preset": "ts-jest/presets/default-esm",
    "extensionsToTreatAsEsm": [".ts"],
    "moduleNameMapper": {
      "^(\\.{1,2}/.*)\\.js$": "$1"
    },

importするファイルの拡張子の表記を、いい感じに調整してくれます。

というのもESMのルールで、importするファイルの拡張子表記が.jsとなり、実態のファイルの拡張子とのズレが生じる場合があります。

例えば、実態のファイルは.tsで作成しているものの、import文で表記されたパスは.jsになっているケースです。

その場合、Jestが依存関係にあるファイルなどを探しにいく際に、見つけられない場合があります。

  • 実ファイル → course.controller.ts
  • import → ./course.controller.js
  • 実行 → ts-jest(トランスパイル)

Jest「course.controller.jsを探しに行こう」
 ↓
Jest「目的のファイルがない(course.controller.tsはあるけど・・・)」
 ↓
Jest「見つからないからエラーを出そう」

FAIL  src/course/course.controller.spec.ts
  ● Test suite failed to run

    Cannot find module './course.controller.js' from 'course/course.controller.spec.ts'

      at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.461 s
Ran all test suites matching course.controller.spec.ts.

そこで、先ほどのmoduleNameMapper": { "^(\\.{1,2}/.*)\\.js$":"$1" }

course.controller.js

course.controller

の様に書き換えてくれます。

これによりcourse.controller.jscourse.controller.ts など、環境に適した拡張子を Jest が自動で補完して探してくれます。

package.jsonのscripts項目を設定

最後に、JestをESM形式で起動するように設定します。

というのも、通常JestはCommonJSモードで起動するため、いくら設定ファイルでESM形式にしても、ズレが生じるからです。

Jest(CommonJSモードで起動) x 設定ファイル(ESM形式) = 合わないからエラーが発生

Cannot use import statement outside a module

そこで、package.jsonのscripts項目に、JestをESM形式で起動するコマンドを定義します。

※package.jsonの中身を一部抜粋

{
  "type": "module",
  "scripts": {
    "test": "NODE_OPTIONS=--experimental-vm-modules jest",
  },
}

このように設定することで、Jestの起動モードと設定ファイルが噛み合い、ESM形式でテストが行える様になります。

あとはターミナル上でnpm test -- course.service.spec.ts と打てば、テストが実行されます。
(course.service.spec.tsの部分はテストコードを記述したファイル名)

最後に

テストコードを書くだけでも大変なのに、Jestの起動で泥沼化するとは予想もしていませんでした。
サポーターの方には大変感謝しています。

もしこの記事が他の誰かの役に立てば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?