Background
I rarely write unit tests normally, but yesterday I was running unit tests on a function using jest, and it kept throwing an error saying that the third-party module was undefined. Below is the abbreviated source code of the function.
// Import a third-party library to parse the URL into an object
import qs from "query-string";
export const getSearch = () => {
return qs.parse(location.search);
};
When I ran the jest command, the console showed an error "cannot read parse of undefined". At first, I thought that jest couldn't load external dependencies because it was running in a sandbox environment. I tried specifying moduleNameMapper
to load external dependencies, but all my attempts failed.
I couldn't figure out the problem, so I searched for the reference to the "query-string" library in the project. It turned out that all the imports were using the default import syntax. Then, I tried calling the getSearch
method in the browser, and there were no errors.
At this point, I had to look for answers in the node_modules folder. To my surprise, I found that the source code was using named exports.
First, I changed the import statement to use named imports and ran the unit tests again.
import { parse } from "query-string";
Then, I thought about this issue. Why were all the imports of the query-string
library using default imports (import qs from "query-string"
) in the project, without any tsError prompts in the IDE or jsError in the browser?
I checked the tsconfig configuration file and found this configuration option: allowSyntheticDefaultImports
. From the name, it seemed like it was causing the issue.
I looked up the definition and explanation of this option on the official TypeScript website:
If allowSyntheticDefaultImports
is set to false:
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
module.exports = {
getStringLength,
};
// @filename: index.ts
// Module '"/home/runner/work/TypeScript-Website/TypeScript-Website/utilFunctions"' has no default export.
import utils from "./utilFunctions";
const count = utils.getStringLength("Check JS");
Suddenly, it became clear. The reason why there were no errors in the IDE was because it allowed "using default import syntax to import external dependencies that do not have default exports". The reason why there were no errors in the browser was because before running the project, Babel would perform an additional layer of export compilation. The transformed module code would look like this:
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
const allFunctions = {
getStringLength,
};
module.exports = allFunctions;
module.exports.default = allFunctions;
Looking at the project's code, there are actually many similar examples, such as the one mentioned above:
import React from "react";
Solution
- Use the
reference
feature of tsconfig to modularize the tsconfig of the test files and set this configuration option to false for local compilation. - Use named imports to load external dependencies.
- For projects that have not been compiled with Babel, it is strongly discouraged to use
allowSyntheticDefaultImports
, as it will cause developers to have no error awareness during development, but encounter a lot of errors when the project is running.