Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest - Could not locate module with dynamic import template string

well I a have a setup with Webpack and Jest for testing react and also using Webpack's aliases using the "@" sign for the directory of my project which is "src/app", I know also it must be mapped into jest.config using the "moduleNameMapper" property to properly align with that alias.

the problem is that there's an <Icon /> component which intern imports dynamically .svg icons using ES dynamic import.

this is the Regex for that mapping for jest.config.js

'^@/': '/src/app/$1'

but it breaks ONLY when testing

everything works fine except when testing

my folder structure is the following:

config/
 webpack-config/

src/
 app/
  assets/
   img/
    some images
   svg/
    icons/
     cart.svg
  components/
   base/
   core/
   shared/
   ...
 client/
  ...
 server/
  ...

this is the Webpack alias I'm using:

module.exports = {
  extensions: ['.js', '.mjs', '.json', '.jsx', '.css'],
  modules: paths.resolveModules,
  alias: {
    '@': paths.srcApp, // that just point
  }
};

this is my jest.config file:

const paths = require('./config/paths');

module.exports = {
  verbose: true,
  collectCoverageFrom: ['src/**/*.{js,jsx,mjs}'],
  setupFiles: [
    '<rootDir>/node_modules/regenerator-runtime/runtime',
    '<rootDir>/config/polyfills.js',
  ],
  // A list of paths to modules that run some code to configure or set up the testing framework before each test.
  setupFilesAfterEnv: ['<rootDir>config/jest/setup.js'],
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}',
    '<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}',
  ],
  testEnvironment: 'node',
  testURL: 'http://localhost',
  modulePaths: ['src'],
  moduleNameMapper: {
    '^@[/](.+)': '<rootDir>/src/app/$1',
  },
  transform: {
    '^.+\\.(js|jsx|mjs)$': '<rootDir>/node_modules/babel-jest',
    '^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
    '^(?!.*\\.(js|jsx|mjs|css|json|svg)$)': '<rootDir>/config/jest/fileTransform.js',
  },
  transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$'],
  moduleDirectories: paths.resolveModules,
  moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'mjs'],
};

this is the component I'm testing

// @flow strict
import React, { useState } from 'react';

import './Icon.scss';

type PropsType = {
  name: string,
  selected?: string,
  size: number | string
};

const Icon = ({ name, size }: PropsType) => {
  const [iconPath, setIconPath] = useState('');
  (async () => {
    const icon = await import(`@/assets/svg/icons/${name}.svg`);
    setIconPath(icon.default);
  })();

  return (
    <img
      alt={`icon ${name}`}
      className="icon"
      style={{
        height: `${size}px`,
        width: `${size}px`,
      }}
      src={iconPath}
    />
  );
};

Icon.defaultProps = {
  size: 16,
};

export default Icon;


and this it's test

// @flow strict
import React from 'react';
import {
  render,
} from 'react-testing-library';

import Icon from './Icon';

describe('Icon', () => {
  it('should have "name" with the path of the icon', () => {
    const { container } = render(<Icon name="cart" />);  
  });
});

and this is the output

Configuration error:

    Could not locate module @/assets/svg/icons/${name}.svg mapped as:
    /Users/jero/Documents/my-shit/react-shop/src/app/assets/svg/icons/${name}.svg.

    Please check your configuration for these entries:
    {
      "moduleNameMapper": {
        "/^@[\/](.+)/": "/Users/jero/Documents/my-shit/react-shop/src/app/$1"
      },
      "resolver": null
    }

the interesting thing is that when I use this regex for jest.config

moduleNameMapper: {
 '/^@\/(.*)$/': '<rootDir>/src/app$1'
}

the error is gone about, but it breaks when a component imports other components when testing:

 FAIL  src/app/components/shared/ImgLoader/ImgLoader.test.js
  ● Test suite failed to run

    Cannot find module '@/components/base/Spinner/Spinner' from 'ImgLoader.js'

    However, Jest was able to find:
        './ImgLoader.js'
        './ImgLoader.scss'
        './ImgLoader.test.js'

    You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx', 'node', 'mjs'].

    See https://jestjs.io/docs/en/configuration#modulefileextensions-array-string

      2 | import React, { useState } from 'react';
      3 | 
    > 4 | import Spinner from '@/components/base/Spinner/Spinner';
        | ^
      5 | 
      6 | import './ImgLoader.scss';
      7 | 

      at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:230:17)
      at Object.<anonymous> (src/app/components/shared/ImgLoader/ImgLoader.js:4:1)

and this is the <ImageLoader />

// @flow strict
import React, { useState } from 'react';

import Spinner from '@/components/base/Spinner/Spinner';

import './ImgLoader.scss';

type PropsType = {
  src: string
};

function ImgLoader({ src }: PropsType) {
  const [imgObj, setImg] = useState({ img: null, isLoading: true });
  const image = new Image();

  image.onload = () => setImg({ img: image.src, isLoading: false });
  image.src = src;

  return (
    imgObj.isLoading ? (<Spinner />) : (<img className="imgLoader img-fluid" src={imgObj.img} alt="img" />)
  );
}

export default ImgLoader;

and its test offcourse

// @flow strict

import React from 'react';
import {
  render,
} from 'react-testing-library';
import ImgLoader from './ImgLoader';

describe('ImgLoader', () => {
  it('tests \'src\'', () => {
    const src = 'https://static.street-beat.ru/upload/resize_cache/iblock/d69/450_450_1/d699afc7b3428f2f51c2f2de6665b506.jpg';
    const { container } = render(<ImgLoader src={src} />);
    console.log('container', container);

  });
});

I believe is something related with the dynamic import?

like image 217
aneurysm Avatar asked May 13 '19 13:05

aneurysm


2 Answers

here's how I fixed it, the problem is that Jest for some reason, it complains when using template literals inside the dynamic import. so to fix it just use normal string concatenation and that's it.

(async () => {
  // $FlowIgnore
  const icon = await import('@/assets/svg/icons/' + name + '.svg');
  setIconPath(icon.default);
})();
like image 67
aneurysm Avatar answered Nov 12 '22 15:11

aneurysm


This can also be solved without removing template literals by replacing Jest's --watch flag with --watchAll

like image 38
Choylton B. Higginbottom Avatar answered Nov 12 '22 17:11

Choylton B. Higginbottom