Next.js + Jest에서 ES Module 패키지 사용하기

Oct 14, 2023
Next.js에서 Jest ES Module 오류 해결


ES Module 패키지를 사용하는 컴포넌트의 테스트를 작성하면서 다음과 같은 오류가 발생했다.
FAIL src/components/Input/Input.test.tsx ● 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 for how to enable it. • If you are trying to use TypeScript, see • 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: For information about custom transformations, see: Details: <...>/node_modules/.pnpm/[email protected]/node_modules/nanoid/index.browser.js:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { urlAlphabet } from './url-alphabet/index.js' ^^^^^^ SyntaxError: Unexpected token 'export'

Jest와 ES Module

Jest는 ESM 옵션을 따로 설정하지 않으면 CommonJS로 동작한다. 따라서 ES Module을 사용하는 코드를 CommonJS로 transpile해야한다.
그러나 기본 설정으로 node_modules는 transpile되지 않으며, transpile하려면 transformIgnorePatterns 설정을 수정해야 한다.

시도 1. transformIgnorePatterns 추가

// jest.config.ts import nextJest from 'next/jest'; import type { Config } from 'jest'; const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: './', }); const esmModules = ['nanoid']; // Add any custom config to be passed to Jest const config: Config = { // Add more setup options before each test is run // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], testEnvironment: 'jest-environment-jsdom', transformIgnorePatterns: [ `node_modules/(?!(?:.pnpm/)?(${esmModules.join('|')}))`, ], }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async export default createJestConfig(config);
위와 같이 transformIgnorePatterns를 추가해도 동일한 오류가 발생했다.

next/jest의 동작

Next.js 12부터 다음 사항을 포함하여 자동으로 Jest를 구성하는 next/jest를 제공한다.
  • SWC를 사용한 transform 설정
  • 스타일시트(.css, .module.css, .scss, .module.scss), 이미지 import, next/font 자동 모킹
  • .env.env의 모든 variant를 process.env로 불러오기
  • test resolving 및 transform에서 node_modules 무시
  • test resolving에서 .next 무시
  • SWC transform을 활성화하는 플래그를 위해 next.config.js 불러오기
따라서 내부적으로 node_modules를 어떻게 제외하는지 확인해보았다.
// ... export default function nextJest(options: { dir?: string } = {}) { // createJestConfig return ( customJestConfig?: | Config.InitialProjectOptions | (() => Promise<Config.InitialProjectOptions>) ) => { // Function that is provided as the module.exports of jest.config.js // Will be called and awaited by Jest return async (): Promise<Config.InitialProjectOptions> => { // ... // Ensure provided async config is supported const resolvedJestConfig = (typeof customJestConfig === 'function' ? await customJestConfig() : customJestConfig) ?? {} const transpiled = (nextConfig?.transpilePackages ?? []).join('|') // ... return { ...resolvedJestConfig, // ... transformIgnorePatterns: [ // To match Next.js behavior node_modules is not transformed, only `transpiledPackages` ...(transpiled ? [ `/node_modules/(?!.pnpm)(?!(${transpiled})/)`, `/node_modules/.pnpm/(?!(${transpiled.replace( /\//g, '\\+' )})@)`, ] : ['/node_modules/']), // CSS modules are mocked so they don't need to be transformed '^.+\\.module\\.(css|sass|scss)$', // Custom config can append to transformIgnorePatterns but not modify it // This is to ensure `node_modules` and .module.css/sass/scss are always excluded ...(resolvedJestConfig.transformIgnorePatterns || []), ], // ... } } } }
첫 번째로, transformIgnorePatterns 가장 앞에 /node_modules/가 존재하기 때문에 뒤에 패키지를 포함하는 패턴을 추가하더라도 계속 무시된다는 것을 확인할 수 있었다.
두 번째로, next.config.js에서 transpilePackages를 가져와서 transformIgnorePatterns에 추가해준다는 것을 확인할 수 있었다.

시도 2. next.config.jstranspilePackages 추가

// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { // ... transpilePackages: ['nanoid'], }; module.exports = nextConfig;
위와 같이 transpilePackages에 문제가 되는 패키지의 이름을 추가해주었고 테스트가 제대로 동작하는 것을 확인할 수 있었다.
> jest -u PASS src/components/Button/Button.test.tsx PASS src/components/Input/Input.test.tsx PASS src/components/Modal/Modal.test.tsx PASS src/components/Spinner/Spinner.test.tsx PASS src/components/Navbar/Navbar.test.tsx PASS src/components/Link/Link.test.tsx Test Suites: 6 passed, 6 total Tests: 34 passed, 34 total Snapshots: 0 total Time: 1.342 s