Map paths for Node modules, for unit testing
Asked Answered
M

3

7

Client side I stub out paths to modules with SystemJS, like this

var systemJsConfig = {
    baseURL: "./",
    defaultJSExtensions: true,
    map: {
        'root-components': 'applicationRoot/rootComponents'
    }
};

and so require('root-components/foo'); would map to applicationRoot/rootComponents/foo.

The problem is, if I run a module with require('root-components/foo'); in Mocha, Node has no clue what that path means. Is there a sane way to accomplish this path mapping in Node?

Is Proxyquire capable of this? I read through their docs but found nothing to indicate it was.

This is just for unit testing, so I'm happy with any solution using any sort of their party utility.

Modena answered 2/6, 2016 at 15:17 Comment(4)
Have you seen this discussion? gist.github.com/branneman/8048520. I believe there are a few solutions/hacks mentioned that would be relevant to what you are trying to do .Satanism
Why would you use a different module load mechanism for tests ? Why not use SystemJs there too ?Tengler
SystemJS is for loading things in the browser. Mocha runs in Node.Modena
@AdamRackis mocha tests can be run in the browser with karma-mochaRathbone
S
6

If your only requirement is so simple you can make a utility function that overrides require.

The new require applies a mapping to the path argument, looks for a module in the new path, and you can optionally fallback for modules that do not exist in the mapped path. The code should look like this

const oldRequire = require;
require = (path) => {
    try {
        const mapped = oldRequire(path.replace('root-components/foo', 'applicationRoot/rootComponents/foo'));
        if (!mapped) {
            throw new Error('module not found in mapped directory');
        }
        console.log('module resolved with mapping', path);
        return mapped;
    } catch (e) {
        console.log('using old require without mapped path', path);
        return oldRequire(path);
    }
};
Saudra answered 14/6, 2016 at 9:40 Comment(1)
I think that's it! Thanks - not sure why I didn't think of that :)Modena
D
2

Option 1 - Modify NODE_PATH(not recommended):

Modify NODE_PATH to include the module path in the shell prior to launching node.js.

exports NODE_PATH=./path/to/module:$NODE_PATH

This is not a great option because it requires a pre-launch step and -- since the NODE_PATH contains many paths -- it's not always clear where the module is being loaded from and there's the possibility of name collisions.

Option 2 - Move the module into an external repo

Lets say you move the components into a separate 'rootcomponents' repo available on your GitHub profile.

Then you can install it directly via:

npm install --save github:arackaf/rootcomponents

Then you should be able to map the project source to a System.js alias.

var systemJsConfig = {
    baseURL: "./",
    defaultJSExtensions: true,
    map: {
        'root-components': 'github:arackaf/rootcomponents'
    }
};

From there it should work as you expected:

require('root-components/foo');

Option 3 - Load the module via relative path:

The config.map option is only for mapping external dependencies to aliases.

One simple alternative is to provide a relative path. Relatives paths are based on the baseURL.

For instance, If you're attempting to load:

src/rootComponents/foo.js

The require would be:

require('./src/rootComponents/foo')

Note: This all assumes that the require() statements are following System.js patterns/rules.

One other possible option is to provide a System.paths[] option that creates an alias to a local path. I can't verify how/whether this'll work (ie I have never tried it) but the specifics can be found here

Danita answered 10/6, 2016 at 17:7 Comment(3)
Yeah, sadly the solution may be to just not use maped paths in any module I need to use in Mocha. sighModena
@AdamRackis Maybe checkout System.config.packages github.com/systemjs/systemjs/blob/master/docs/…. Looks like you can specify commonjs as the module format and map local paths to aliases.Danita
Sure, but mapping local paths to aliases is the root of my problem - Node doesn't understand those aliases and throws errors when I try to run those modules through Mocha.Modena
O
1

You could do module aliasing a lot of different ways as described in this gist, but the simpler way is just to set the starting root path with NODE_PATH, and go from there:

Here's what it looks like with the NODE_PATH environment variable:

NODE_PATH=./src node src/server/index.js

Then in all your files, no matter where they are in the hierarchy, will resolve from the ./src directory. This eliminates the need for aliases like you describe, but I realize that in your case, may require moving/renaming many files.

Example: require('root-components/foo'); => ./src/root-components/foo.js

Olympias answered 10/6, 2016 at 15:10 Comment(1)
Thanks for the good info - it looks like the patterns in that gist are trying to solve a different problem, namely, eliminating stuff like ../../../../foo/bar. Client-side JavaScript loaders provide custom path resolution simply. I'm just trying to see if there's some hack to make those custom paths work in Node for unit testing. The problem for me with setting a global NODE_PATH is that it'll affect everything loaded, not just the few things sitting in root-componentsModena

© 2022 - 2024 — McMap. All rights reserved.