I am working in a Vite project, my goal is to be able to write tests. So I setup Jest and Babel and write a test, but I’m getting an error like this:
C:UsersjoaovOneDriveDev ProjetosProjetos pessoaiscsgo-e-commercenode_modulesfirebaseappdistindex.esm.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { registerVersion } from '@firebase/app';
^^^^^^
SyntaxError: Cannot use import statement outside a module
> 1 | import { initializeApp } from 'firebase/app';
| ^
2 | import {
3 | getAuth,
4 | signInWithRedirect,
This is the test I am trying to write:
// ProductCard.test.tsx
import { render, screen } from '@testing-library/react';
import { Theme } from '../../Theme';
import ProductCard from './ProductCard';
import { Provider } from 'react-redux';
import { store } from '../../store/store';
import '@testing-library/jest-dom';
const product = {
id: 1,
imageUrl: 'www.google.com',
name: 'Dragon Lore',
price: 90,
};
describe('ProductCard', () => {
it('should render the Product Card', () => {
render(
<Provider store={store}>
<Theme>
<ProductCard product={product} />
</Theme>
</Provider>
);
const nameElement = screen.getByText(/dragon lore/i);
const imageElement = screen.getByAltText(/dragon lore/i);
const button = screen.getByRole('button', { name: /add to cart/i });
expect(nameElement).toBeInTheDocument();
expect(imageElement).toBeInTheDocument();
expect(button).toBeInTheDocument();
});
});
Following is my Jest configuration:
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom',
setupFilesAfterEnv: ['<rootDir>/.jest/setup-tests.js'],
moduleNameMapper: {
'\.(gif|ttf|eot|svg|png)$': '<rootDir>/.jest/__mocks__/fileMock.js',
'\.(css|less|sass|scss)$': 'identity-obj-proxy',
},
transform: {
'^.+\.jsx?$': 'babel-jest',
'^.+\.tsx?$': 'ts-jest',
},
};
The Babel configuration is:
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { esmodules: true, node: 'current' } }],
'@babel/preset-typescript',
['@babel/preset-react', { runtime: 'automatic' }],
],
};
And, the TypeScript configuration is:
{
"compilerOptions": {
"target": "es5",
"types": ["vite/client", "node"],
"lib": ["dom", "dom.iterable", "esnext"],
"downlevelIteration": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "CommonJS",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
Finally, this is my package.json
file:
{
"name": "csgo-e-commerce",
"private": true,
"version": "0.0.0",
"type": "commonjs",
"scripts": {
"dev": "vite",
"test": "jest",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@stripe/react-stripe-js": "^1.16.4",
"@stripe/stripe-js": "^1.46.0",
"@types/react-router-dom": "^5.3.3",
"dotenv": "^16.0.3",
"firebase": "^9.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.40.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7",
"stripe": "^11.10.0",
"styled-components": "^5.3.6",
"typed-redux-saga": "^1.5.0",
"validator": "^13.7.0",
"vite-plugin-svgr": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/plugin-transform-modules-commonjs": "^7.21.2",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.0",
"@types/node": "^18.14.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/redux-logger": "^3.0.9",
"@types/styled-components": "^5.1.26",
"@types/validator": "^13.7.13",
"@vitejs/plugin-react": "^2.2.0",
"babel-jest": "^29.4.3",
"babel-loader": "^8.3.0",
"babel-plugin-macros": "^3.1.0",
"eslint": "^8.28.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.4.3",
"prettier": "^2.7.1",
"redux-logger": "^3.0.6",
"ts-jest": "^29.0.5",
"typescript": "^4.9.5",
"vite": "^3.2.3"
}
}
2
Answers
So the problem was Firebase, I solve the problem with this final config:
Jest config:
Babel Config:
Package:
I follow the 'transformIgnorePatterns' suggested, installed 'babel/plugin-transform-runtime' add this line in babel config:
I ran my tests again and it worked! Thank you!!
There are multiple reasons why this error can happen.
The most common reason is that the package that you are using doesn’t have
"type": "module"
property declared in itspackage.json
file. So, in that case, any file with.js
extension is treated as CommonJS file which doesn’t allow ESM Syntax –import
andexport
statements. And, the file with.js
extension is using ESM syntax.The second reason is that the third party package is ESM-only package and the compiler/test-runner/bundler is not compiling the file as it is considered third-party packages. Generally, bundler or compiler would not attempt to compile any file inside
node_modules
folder.The third possible reason is that your package is dual-published supporting both ESM and CommonJS using conditional
exports
field of package.json. But, your bundler or test runner is probably not picking up the right file in question.Now let’s return to your problem. You are using latest version (v29) of Jest meaning
exports
field is supported and it can also handle ESM modules well.That leaves us with first problem where
package.json
file is not probably declaring if this is a ESM module or not. The problematic package in question isfirebase
package. If we look inside the package:The
<ROOT>/node_modules/firebase/app/package.json
file, it properly declares THEexports
field with conditional exports.In your code, somewhere you are importing
firebase/app
and in your Jest configuration, then environment you are using isjest-environment-jsdom
which means it matches thebrowser
condition and it picks following:From this it is picking up
./app/dist/esm/index.esm.js
as evident from the error message. And, why Jest usesimport
instead ofrequire
is due to the heuristics it is applying (your own code is in ESM and Babel is running in ESM target). This is good so far.But right when it picks up
./app/dist/esm/index.esm.js
file, the problem begins. Since, this is.js
file, Jest doesn’t really know if this isESM
orCommonJS
file. The obvious thing that jest will do is to look for nearestpackage.json
file and there is one present here<ROOT>/node_modules/firebase/app/package.json
. This is not the same as<ROOT>node_modules/firebase/package.json
. This nearest package.json file has following content:As, you can see this file doesn’t specify
"type": "module"
field and thus Jest assumes that<ROOT>/firebase/app/dist/esm/index.esm.js
is a legacy CommonJS file instead of a new ESM file.It is not your mistake per say. But it is rather package author’s mistake that they shipped this additional
package.json
file. Ideally, one package should have exactly onepackage.json
file. But in earlier days, when things were still infancy, this was a common practice to nest submodules and exploit node resolution algorithm. But with arrival ofexports
field, this is no longer necessary.That is why you get this error. Now to fix this error, you have to tell Jest to transform this file if necessary using the
transformIgnorePatterns
configuration. I am bit hazy on regular expression but it would be something like this:This is a long answer but I hope it helps you understand the exact cause of the issue.