59

I'm developing a web app at work in React/Redux/Webpack and am now starting to integrate testing with Mocha.

I followed the instructions for writing tests in the Redux documentation, but now I have run into an issue with my webpack aliases.

For example, take a look at the imports section of this test for one of my action creators:

import expect       from 'expect'                 // resolves without an issue
import * as actions from 'actions/app';           // can't resolve this alias
import * as types   from 'constants/actionTypes'; // can't resolve this alias

describe('Actions', () => {
  describe('app',() => {
    it('should create an action with a successful connection', () => {

      const host = '***************',
            port = ****,
            db = '******',
            user = '*********',
            pass = '******';

      const action = actions.createConnection(host, port, db, user, pass);

      const expectedAction = {
        type: types.CREATE_CONNECTION,
        status: 'success',
        payload: { host, port, database, username }
      };

      expect(action).toEqual(expectedAction);
    });
  });
});

As the comments suggest, mocha isn't able to resolve my import statements when they are referencing aliased dependencies.

Because I'm still new to webpack, here's my webpack.config.js:

module.exports = {
  devtool: 'eval-source-map',
  entry: [
    'webpack-hot-middleware/client',
    './src/index'
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  resolve: {
    extensions : ['', '.js', '.jsx'],
    alias: {
      actions: path.resolve(__dirname, 'src', 'actions'),
      constants: path.resolve(__dirname, 'src', 'constants'),
      /* more aliases */
    }
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
      exclude: /node_modules/,
      include: __dirname
    }]
  }
};

Also, I'm using the command npm test to run mocha, here's the script I'm using in my package.json.

 {   
   "scripts": {
     "test": "mocha ./src/**/test/spec.js --compilers js:babel-core/register --recursive"
   }
 }

So here's where I get stuck. I need to include the aliases from webpack into mocha when it runs.

Danny Delott
  • 6,756
  • 3
  • 33
  • 57

8 Answers8

42

Okay so I realized that everything I was aliasing was in the src/ directory, so I simply needed to modify my npm run test script.

{   
  "scripts": {
    "test": "NODE_PATH=./src mocha ./src/**/test/spec.js --compilers js:babel-core/register --recursive"
  }
}

Probably won't work for everyone, but that solved my issue.

Danny Delott
  • 6,756
  • 3
  • 33
  • 57
  • Thanks I'll try this. – Issam Zoli Dec 23 '15 at 10:59
  • All of my stuff is also in `src`, but not sure how to implement your fix to my test script: `find ./src -name '*.test.js' | xargs mocha --require babel-core/register` – azium Jan 16 '16 at 20:16
  • This is a most excellent solution, especially compared to mocking and proxying require!! – mikeycgto Mar 12 '16 at 19:49
  • this works great for me for a single test run, but I'm not able to get successfully watch files in an aliased directory, have you had any success watching? – jmancherje Sep 12 '16 at 19:04
  • 1
    I've started using `mocha-webpack` instead of this workaround. https://github.com/zinserjan/mocha-webpack – Danny Delott Sep 12 '16 at 19:06
15

You can also use a babel plugin I authored: https://github.com/trayio/babel-plugin-webpack-alias It will convert your aliased path to relative paths just by including a babel plugin to your .babelrc.

Louis
  • 146,715
  • 28
  • 274
  • 320
adriantoine
  • 2,160
  • 1
  • 15
  • 14
  • I clicked on your link and judging by the user information there and your user information here, it looks like you are the author of the plugin, so I edited accordingly, because it is against SO rules to mention software you created in an answer *without* being explicit that you authored it. (Doing so is considered spam, and carries severe penalties.) If somehow you are not the author, you can rollback my edit but make sure to clarify because otherwise, the next person who clicks the link risks making the same inference I did. – Louis Feb 23 '16 at 17:13
  • Also, this answer as it stands is borderline link-only. Adding information about how to install and use the plugin would fix this issue. – Louis Feb 23 '16 at 17:14
  • 4
    I'm not using this in production, but I am developing with it now. It seems to do exactly as expected. I'm a fan of alias, and I think using a babel-plugin to solve this problem is the way to go. I haven't looked at the source or anything, but so far works fine. – Ryan Ore Apr 20 '16 at 23:47
5

I also encountered the same problem, but with this plugin I solved it.

https://www.npmjs.com/package/babel-plugin-webpack-aliases

The execution command of your "mocha" is not reading the webpack.config.js, so it can not resolve the alias.
By setting this plugin, consider webpack.config.js when compiling with "babel-core/register". As a result, the alias will also be valid during testing.

npm i -D babel-plugin-webpack-aliases

and add this setting to .babelrc

{
    "plugins": [
        [ "babel-plugin-webpack-aliases", { "config": "./webpack.config.js" } ] 
    ]
}
Yuki Hirano
  • 49
  • 1
  • 4
4

I think I solved this problem. You should use 2 package: mock-require and proxyquire.

Assuming you have a js file like this:

app.js

import action1 from 'actions/youractions';   

export function foo() { console.log(action1()) }; 

And your test code should write like this:

app.test.js

import proxyquire from 'proxyquire';
import mockrequire from 'mock-require';

before(() => {
  // mock the alias path, point to the actual path
  mockrequire('actions/youractions', 'your/actual/action/path/from/your/test/file');
  // or mock with a function
  mockrequire('actions/youractions', {actionMethod: () => {...}));

let app;
beforeEach(() => {
  app = proxyquire('./app', {});
});

//test code
describe('xxx', () => {
  it('xxxx', () => {
    ...
  });
});

files tree

app.js
  |- test
    |- app.test.js

First mock the alias path by mock-require in before function, and mock your test object by proxyquire in beforeEach function.

zzm
  • 645
  • 1
  • 5
  • 19
4

Danny's answer is great. But my situation is a little bit different. I used webpack's resolve.alias to use all the files under src folder.

resolve: {
  alias: {
    '-': path.resolve(__dirname, '../src'),
  },
},

and use a special prefix for my own modules like this:

import App from '-/components/App';

To test code like this I have to add a command ln -sf src test/alias/- before mocha test and use the NODE_PATH=./test/alias trick Danny camp up with. So the final script would be like this:

{   
  "scripts": {
    "test": "ln -sf src test/alias/-; NODE_PATH=./test/alias mocha ./src/**/test/spec.js --compilers js:babel-core/register --recursive"
  }
}

PS:

I used - because beautifal charactors like @ or ~ are not safe enough. I found the answer for safe characters here

Stupidism
  • 113
  • 1
  • 2
  • 7
2

I assume you would have --require babel-register in your mocha.opts. You can use babel module resolver plugin https://github.com/tleunen/babel-plugin-module-resolver. This allows you to set alias in .babelrc, similar to your webpack alias:

{
  "plugins": [
    ["module-resolver", {
       "alias": {
         "actions": "./src/actions",
         "constants": "./src/constants"
       }
    }]
  ]
}
Northern
  • 2,338
  • 1
  • 17
  • 20
1

I had the exact same issue. It seems impossible to use webpack aliases in require.js or in node's require.

I ended up using mock-require in the unit tests and just replacing the paths manually, like this:

var mock = require('mock-require');

mock('actions/app', '../../src/actions/app');

mock-require is a good tool, because most likely you'd like to mock most of your dependencies anyway instead of using the actual scripts from src.

Ilya Kogan
  • 21,995
  • 15
  • 85
  • 141
  • It seems no work when I use mock-require, error still say can't find module 'actions/xxx'. – zzm Dec 13 '15 at 05:25
1

To keep aliases in one place like config/webpack.common.js

resolve: {
    alias: {
        '~': path.resolve(__dirname, './../src/app')
    }
}

then install babel-plugin-webpack-alias

npm i babel-plugin-webpack-alias --save-dev

then in .babelrc put :

{
    "presets": ["env"],
    "plugins": [
        ["babel-plugin-webpack-alias", {
            "config": "./config/webpack.common.js" // path to your webpack config
        }]
    ]
}
ScorpAL
  • 58
  • 5