New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems with resolving aliases #1290

Closed
papermana opened this Issue Jul 13, 2016 · 10 comments

Comments

Projects
None yet
3 participants
@papermana
Copy link
Contributor

papermana commented Jul 13, 2016

I'm trying to set Jest to work with my webpack aliases and to achieve this I'm using the moduleNameMapper property in the config. Problem is, alias resolution is very finicky for me here. I actually created a repo with some very simple code to replicate the issue: https://github.com/papermana/jest-replicating-issue.

I'm using the most recent versions of both Node and Jest. In my case, if I open this project in the console and run npm test, I'm going to see an error: Cannot find module '@stores/exampleStore' from 'exampleStore.test.js'.

Now I go to exampleStore.test.js and add .js at the end of the path on the first line:

jest.unmock('@stores/exampleStore.js');

const example = require('@stores/exampleStore');

Run npm test again, and it passes! Okay, so for consistency's sake add .js to the path on the third line as well. Now it shows the same error again. Remove the extension from the first line:

jest.unmock('@stores/exampleStore');

const example = require('@stores/exampleStore.js');

Passes!

So now a different solution:

// jest.unmock('@stores/exampleStore');

const example = require.requireActual('@stores/exampleStore.js');

Passes again. Let's remove the extension then, since it should work anyway:

// jest.unmock('@stores/exampleStore');

const example = require.requireActual('@stores/exampleStore');

Shows Error: ENOENT: no such file or directory.

Of course, I'm not sure exactly if this really is a problem with aliases, but it does work when put this way:

jest.unmock('../exampleStore');

const example = require('../exampleStore');
@cpojer

This comment has been minimized.

Copy link
Contributor

cpojer commented Jul 14, 2016

I would probably suggest disabling automocking altogether and doing "automock": false in the config.

It does seem like for unmocking/mocking we don't properly take the moduleNameMapper into account.

@papermana

This comment has been minimized.

Copy link
Contributor

papermana commented Jul 14, 2016

Sorry for saying this, but doesn't that defeat the whole point of using Jest? Seems like this problem is something that ruins the idea of using moduleNameMapper for alias resolution.

@cpojer

This comment has been minimized.

Copy link
Contributor

cpojer commented Jul 14, 2016

No, Jest has a lot more to offer than automocking. Automocking is useful in certain situations, like when you have a codebase that isn't tested at all with a huge amount of tightly connected dependencies. We have found at Facebook that after years of it being beneficial, it is not providing as much value as it once did and people choose to disable it more often.

Jest itself has a ton of other features that are critical to Facebook. I refer to the website for a subset of those features.

Regarding this individual bug, it doesn't look like I'm gonna get to this any time soon as I'm focused on Jest's performance and some other developer experience work for the next few months. If you'd like to help out to fix this, it is likely going to be somewhere around here:

_normalizeID(from: Path, moduleName?: ?string) {
and you might need to look into the jest-resolve package as well.

@papermana

This comment has been minimized.

Copy link
Contributor

papermana commented Jul 14, 2016

Does moduleNameMapper automatically unmock requires? This is only indirectly suggested in the docs but is stated in https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/__tests__/Runtime-requireModuleOrMock-test.js#L96.

This seems useful when you're using this functionality to provide mocks for assets like CSS files, but is kind of at odds with the idea of using webpack-like aliases, since the behavior changes depending on whether an alias is used or not. For instance:

const example = require('@stores/exampleStore');

describe('exampleStore', () => {
  it('does something', () => {
    expect(example()).toBe('store');
  })
})

This passes, since Jest actually imports the file required. But this:

const example = require('../exampleStore');

describe('exampleStore', () => {
  it('does something', () => {
    expect(example()).toBe('store');
  })
})

...shows an error: Expected undefined to be 'store'. since the file gets mocked.

Could this be the root (or at least part) of the problem here? Calling jest.unmock(path) when the path is already not being mocked?

@cpojer

This comment has been minimized.

Copy link
Contributor

cpojer commented Jul 14, 2016

Yap, that does sound like it. If I remember correctly, the first implementation of moduleNameMapper was made for the stubs use case (like CSS etc.) which we didn't ever want to be mocked.

@papermana

This comment has been minimized.

Copy link
Contributor

papermana commented Jul 14, 2016

Perhaps it could be useful to provide a way of specifying whether a particular key/regexp should be automatically unmocked or not. Otherwise, you're forced to let go of automocking, plus if you ever mix relative paths and aliases, you get two differing behaviors. I think there should also be a note in the documentation that moduleNameMapper unmocks everything, in case others wanted to use it for aliases and got confused.

In the meantime, a script preprocessor seems like still the best way to get aliases.

@cpojer

This comment has been minimized.

Copy link
Contributor

cpojer commented Jul 14, 2016

I'm gonna close this as wontfix. Your PR adds a note to the documentation and it doesn't seem like anyone is going to get to working on this any time soon. Of course, I'm always happy to help someone if they want to make a PR to change this.

@cpojer cpojer closed this Jul 14, 2016

@glortho

This comment has been minimized.

Copy link

glortho commented Dec 14, 2016

To boil this down, it's not possible to use auto-mocking for any modules you've put in moduleNameMapper.

This is perhaps most problematic when you have a webpack alias (as we do) for your root src directory that is then used extensively throughout the codebase. For example ~/lib/someUtil.js.

If you put "^~/": "<rootDir>/src/" in moduleNameMapper, no modules imported with ~/ will be automocked. So I think options are:

  1. Manually mock all such modules in every test.

  2. In moduleNameMapper, explicitly map any modules that should always be mocked to their mock. You'll then just have to use a different path when you want to test the actual module.

  3. Use regex to differentiate between mocks and actuals, something like this pseudo-code:

"^~/(.*)/(.*)(?:actual)$": "<rootDir>/src/$1/$2", // require( '~/path/foo:actual' ) => '<rootDir>/src/path/foo'
"^~/(.*)/(.*)$": "<rootDir>/src/$1/__mocks__/$2" // require( '~/path/foo' ) => '<rootDir>/src/path/__mocks__/foo'

Though in this case you'd have to make sure there was some kind of resolvable module in mocks for every module.

It would be nice if there were an option just to use webpack resolution to locate modules without affecting mock choices. Until then, we're going the route of manual mocking.

@papermana

This comment has been minimized.

Copy link
Contributor

papermana commented Dec 14, 2016

@glortho
There is a fourth option, which is to use a script preprocessor. That plugs into Jest and processes each file before it is executed, for instance using simple regex to replace custom aliases with absolute paths. Then Jest can automock these files since it's not even aware they're any different than regular requires/imports.

If you were interested, an example of such a preprocessor is a package called jest-webpack-alias. It didn't work for me for some reason I don't remember so I also wrote my own version, jest-alias-preprocessor. And lastly, jest-webpack-alias readme apparently recommends using babel-plugin-module-resolver nowadays but I can't tell you anything about it as I've never used it.

@glortho

This comment has been minimized.

Copy link

glortho commented Dec 14, 2016

Thanks for the response @papermana . We tried jest-webpack-alias and babel-plugin-module-resolver with no luck, but didn't know about jest-alias-preprocessor so we'll give that one a try. Thanks for the tip!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment