58

I use webpack to develop a React component. Here is a simple version of it:

'use strict';

require('./MyComponent.less');

var React = require('react');

var MyComponent = React.createClass({
  render() {
    return (
      <div className="my-component">
        Hello World
      </div>
    );
  }
});

module.exports = MyComponent;

Now, I would like to test this component using jest. Here is the relevant bit from my package.json:

"scripts": {
  "test": "jest"
},
"jest": {
  "rootDir": ".",
  "testDirectoryName": "tests",
  "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
  "unmockedModulePathPatterns": [
    "react"
  ]
}

When running npm test, I get the following error:

SyntaxError: /Users/mishamoroshko/react-component/src/tests/MyComponent.js: /Users/mishamoroshko/react-component/src/MyComponent.js: /Users/mishamoroshko/react-component/src/MyComponent.less: Unexpected token ILLEGAL

Looks like webpack needs to process require('./MyComponent.less') before jest can run the test.

I wonder if I need to use something like jest-webpack. If yes, is there a way to specify multiple scriptPreprocessors? (note that I already use babel-jest)

Cihan Keser
  • 3,190
  • 4
  • 30
  • 43
Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746
  • Have you tried to compile your test files with webpack into a separate directory and then running jest on the generated files? – badsyntax Mar 07 '15 at 09:08
  • I've also had this issue and have a similar question [here](http://stackoverflow.com/questions/31547587/testing-webpack-built-react-components-with-jest) outlining the approaches I have tried (and that are also mentioned here) and why they are not sufficient. – Matt Derrick Sep 24 '15 at 10:50

11 Answers11

26

The cleanest solution I found for ignoring a required module is to use the moduleNameMapper config (works on the latest version 0.9.2)

The documentation is hard to follow. I hope the following will help.

Add moduleNameMapper key to your packages.json config. The key for an item should be a regex of the required string. Example with '.less' files:

"moduleNameMapper": { "^.*[.](less|LESS)$": "EmptyModule" },

Add a EmptyModule.js to your root folder:

/**
 * @providesModule EmptyModule
 */
module.exports = '';

The comment is important since the moduleNameMapper use EmptyModule as alias to this module (read more about providesModule).

Now each require reference that matches the regex will be replaced with an empty string.

If you use the moduleFileExtensions configuration with a 'js' file, then make sure you also add the EmptyModule to your 'unmockedModulePathPatterns'.

Here is the jest configuration I ended up with:

"jest": {
  "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
  "moduleFileExtensions": ["js", "json","jsx" ],
  "moduleNameMapper": {
    "^.*[.](jpg|JPG|gif|GIF|png|PNG|less|LESS|css|CSS)$": "EmptyModule"
  },
  "preprocessorIgnorePatterns": [ "/node_modules/" ],
  "unmockedModulePathPatterns": [
    "<rootDir>/node_modules/react",
    "<rootDir>/node_modules/react-dom",
    "<rootDir>/node_modules/react-addons-test-utils",
    "<rootDir>/EmptyModule.js"
  ]
}
Tyler
  • 21,762
  • 11
  • 61
  • 90
ZahiC
  • 13,567
  • 3
  • 25
  • 27
  • Confirmed this works, just make sure you have nothing in your `EmptyModule.js` before the comment (like another comment) – mpint Aug 15 '16 at 00:53
  • This is also the solution that Facebook's [create-react-app](https://github.com/facebookincubator/create-react-app) uses. Modified a tiny bit, but it is the same technique. You'd be able to see the implementation very clearly if you `npm run eject` on a new `create-react-app` project. – Don Dec 02 '16 at 20:24
  • that worked, but...is it really the cleanest solution? it would be nice if we could tell Jest to ignore certain files without having to create an empty module – zok Mar 01 '17 at 15:46
  • This doesnt seem to make any difference to mine whatsoever. still getting the same `unexpected token .` error. – Aid19801 May 22 '19 at 12:11
20

I ended up with the following hack:

// package.json

"jest": {
  "scriptPreprocessor": "<rootDir>/jest-script-preprocessor",
  ...
}


// jest-script-preprocessor.js
var babelJest = require("babel-jest");

module.exports = {
  process: function(src, filename) {
    return babelJest.process(src, filename)
      .replace(/^require.*\.less.*;$/gm, '');
  }
};

But, I'm still wondering what is the right solution to this problem.

Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746
  • I have yet to find a better solution? Unless anyone knows of a patch. I feel so icky doing this :( - Alas thank you for the work around. – The Dembinski Oct 09 '16 at 19:48
  • Wouldn't [modulePathIgnorePatterns](https://facebook.github.io/jest/docs/configuration.html#modulepathignorepatterns-array-string) achieve the same without code ? There's also [modulePaths](https://facebook.github.io/jest/docs/configuration.html#modulepaths-array-string), I was able to use it with webpack aliases. It also needs a simlink to match the alias. Nasty hacks... – Alpar Oct 11 '16 at 13:13
  • Don't know why it doesn't work for me, still see same error :( – Tien Do Nov 07 '16 at 14:24
  • jest.moduleNameMapper with mock files is the way to go, documented [here](https://facebook.github.io/jest/docs/webpack.html#content). (unless you are stuck on older versions of Jest – Samjones Apr 27 '17 at 18:35
10

I just found that it's even simpler with Jest's moduleNameMapper configuration.

// package.json

"jest": {
    "moduleNameMapper": {
      "^.+\\.scss$": "<rootDir>/scripts/mocks/style-mock.js"
    }
}

// style-mock.js

module.exports = {};

More detail at Jest's tutorial page.

Karl Wenzel
  • 2,412
  • 25
  • 24
Tien Do
  • 10,319
  • 6
  • 41
  • 42
  • 1
    Nowadays this is the best approach, and today Jest's documentation that you linked to pretty clear explains what you need to do. I've managed to set it up pretty quickly with it, even though my setup even includes TypeScript. – Vincent Jan 06 '17 at 22:57
4

I recently released Jestpack which might help. It first builds your test files with Webpack so any custom module resolution/loaders/plugins etc. just work and you end up with JavaScript. It then provides a custom module loader for Jest which understands the Webpack module runtime.

Richard Scarrott
  • 6,638
  • 1
  • 35
  • 46
3

From Jest docs:

// in terminal, add new dependency: identity-obj-proxy
npm install --save-dev identity-obj-proxy

// package.json (for CSS Modules)
{
  "jest": {
    "moduleNameMapper": {
      "\\.(css|less)$": "identity-obj-proxy"
    }
  }
}

The snippet above will route all .less files to the new dependency identity-obj-proxy, which will return a string with the classname when invoked, e.g. 'styleName' for styles.styleName.

tbraun
  • 2,636
  • 31
  • 26
2

I think a less hacky solution would be to wrap your preprocessor in a conditional on the filename matching a javascript file:

if (filename.match(/\.jsx?$/)) {
    return babelJest.process(src, filename);
} else {
    return '';
}

This works even if you don't explicitly set the extension in the require line and doesn't require a regex substitution on the source.

Krustal
  • 518
  • 1
  • 4
  • 12
  • Be careful with this - I had a similar preprocessor, and ended up getting `TypeError: React.createClass is not a function` as the `return '';` statement means that you "throw away" things like included `node_modules` `.js` files.
    Likely this was an issue with my `package.json` - there's probably a way to ignore those files, but I think it's prudent to rewrite the above lines as: ```if (filename.match(/\.jsx?$/)) { return babelJest.process(src, filename); } else { return src; //change here }```
    – schimmy Feb 22 '16 at 05:24
1

I have experienced similar issue with such pattern

import React, { PropTypes, Component } from 'react';
import styles from './ContactPage.css';
import withStyles from '../../decorators/withStyles';

@withStyles(styles)
class ContactPage extends Component {

see example at https://github.com/kriasoft/react-starter-kit/blob/9204f2661ebee15dcb0b2feed4ae1d2137a8d213/src/components/ContactPage/ContactPage.js#L4-L7

For running Jest I has 2 problems:

  • import of .css
  • applying decorator @withStyles (TypeError: <...> (0 , _appDecoratorsWithStyles2.default)(...) is not a function)

First one was solved by mocking .css itself in script preprocessor.

Second one was solved by excluding decorators from automocking using unmockedModulePathPatterns

module.exports = {
  process: function (src, filename) {

    ...

    if (filename.match(/\.css$/)) src = '';

    ...

    babel.transform(src, ...
  }
}

example based on https://github.com/babel/babel-jest/blob/77a24a71ae2291af64f51a237b2a9146fa38b136/index.js

Note also: when you working with jest preprocessor you should clean cache:

$ rm node_modules/jest-cli/.haste_cache -r
x'ES
  • 554
  • 5
  • 15
0

Taking inspiration from Misha's response, I created an NPM package that solves this problem while also handling a few more scenarios I came across:

webpack-babel-jest

Hopefully this can save the next person a few hours.

Carlos Atencio
  • 799
  • 7
  • 8
0

If you're using babel, you can strip unwanted imports during the babel transform using something like https://github.com/Shyp/babel-plugin-import-noop and configuring your .babelrc test env to use the plugin, like so:

{
  "env": {
    "development": {
      ...
    },
    "test": {
      "presets": [ ... ],
      "plugins": [
        ["import-noop", {
          "extensions": ["scss", "css"]
        }]
      ]
    }
  }
}
Tarrence
  • 794
  • 1
  • 6
  • 14
0

We had a similar problem with CSS files. As you mentioned before jest-webpack solves this problem fine. You won't have to mock or use any module mappers either. For us we replaced our npm test command from jest to jest-webpack and it just worked.

Cogwizzle
  • 550
  • 4
  • 14
-1

Webpack is a great tool, but I don't need to test it's behavior with my Jest unit tests, and adding a webpack build prior to running unit tests is only going to slow down the process. The text-book answer is to mock non-code dependencies using the "moduleNameMapper" option

https://facebook.github.io/jest/docs/webpack.html#handling-static-assets

Samjones
  • 278
  • 2
  • 9