背景#
平常ほとんど単体テストを書かないが、昨日 jest を使ってある関数の単体テストを実行したところ、常に三方モジュールが undefined であると報告された。以下は縮小された関数のソースコードである。
// 三方ライブラリをインポートして URL を Object に解析する
import qs from "query-string";
export const getSearch = () => {
return qs.parse(location.search);
};
この時 jest コマンドを実行すると、コンソールに cannot read parse of undefined
というエラーが表示された。最初は jest が外部依存関係を読み込めないのかと思い、サンドボックス環境で実行していた。外部依存関係を読み込むために moduleNameMapper
を指定する必要があり、その後の試みはすべて失敗に終わった。
私は理解できず、プロジェクト内で "query-string" ライブラリの参照を全文検索したが、すべてデフォルトインポートの書き方だった。そこでブラウザで getSearch
メソッドを呼び出してみたが、エラーは出なかった。
この時、私は node_modules の中で答えを探すしかなかった。驚くべきことに、ソースコードはすべて具名エクスポートの書き方だった。
まずはインポートの方法を具名インポートに変更し、単体テストを実行してみる。
import { parse } from "query-string";
振り返ってこの問題を考える。なぜプロジェクト内の query-string
ライブラリの参照はすべてデフォルトインポートの書き方 import qs from "query-string"
で、IDE では tsError の警告がなく、ブラウザでも jsError のエラーが出ないのか?
tsconfig の設定ファイルを見返すと、この設定項目 allowSyntheticDefaultImports
を発見した。翻訳すると:合成デフォルトインポートを許可する。名前からして、これが原因のように感じた。
ts の公式サイトでその定義を調べてみる:
もし allowSyntheticDefaultImports
が false の場合
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
module.exports = {
getStringLength,
};
// @filename: index.ts
// モジュール '"/home/runner/work/TypeScript-Website/TypeScript-Website/utilFunctions"' にデフォルトエクスポートがありません。
import utils from "./utilFunctions";
const count = utils.getStringLength("Check JS");
はっと気づいた。IDE で ts がエラーを出さなかったのは、デフォルトエクスポートのない外部依存関係をデフォルトインポート方式でインポートすることを許可しているからだった。ブラウザでエラーが出なかったのは、プロジェクトを実行する前に babel がエクスポートのコンパイルを行い、変換されたモジュールコードは以下のようになるからである。
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
const allFunctions = {
getStringLength,
};
module.exports = allFunctions;
module.exports.default = allFunctions;
プロジェクトのコードを見返すと、実際にはこのような例がたくさんある。上記の例のように
import React from "react";
解決#
- tsconfig の reference 機能を使用して、テストファイルの tsconfig をモジュール化し、この設定項目を false に設定して局所的にコンパイルする。
- 具名インポートの方法で外部依存関係を読み込む。
- babel コンパイルを行っていないプロジェクトでは、allowSyntheticDefaultImports の使用を厳禁とする。そうしないと、開発者はエラーに気づかずに開発を進めることになり、プロジェクト起動時に多くのエラーが発生する。