Skip to content
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

td.replace() on an un-parseable source will claim the module is missing instead of printing the parse error #320

Closed
fantapop opened this issue Dec 30, 2017 · 12 comments
Labels

Comments

@fantapop
Copy link

I've just completed an upgrade to my project to use webpack and with it module import statements. Our tests stopped working. The failure I was seeing was:

 Cannot find module '../lib/dao' from '/Users/chris/Development/publisher'

After a bit digging, I found this bit of code:

var requireAt = function requireAt(path) {
    console.log('in requireAt for path: ', path);
  try {
    // 1. Try just following quibble's inferred path
    return require(_quibble2.default.absolutify(path));
  } catch (e) {
    // 2. Try including npm packages
    return require(_resolve2.default.sync(path, { basedir: process.cwd() }));
  }
};

My files were failing to require but the error here is getting squashed under the assumption that the file could not be found. After adding more verbose logging I can see the following error:

failed to require:  /Users/chris/Development/publisher/lib/dao.js:1
(function (exports, require, module, __filename, __dirname) { import config from 'config';
                                                              ^^^^^^

SyntaxError: Unexpected token import

Maybe you can inspect the error that is getting thrown and look for file not found or something and throw all others.

@fantapop
Copy link
Author

As I get further into this, I'm not sure how to proceed. Do you have any tips on using testdouble with webpacked source. If quibble looking for a specific filename to include and webpack has changed the name during the build, this is no bueno.

@searls
Copy link
Member

searls commented Dec 31, 2017

td.replace of modules only works in Node.js. The browser distribution of the library is committed to dist/testdouble.js in the repo and is probably what you should point to in your webpack config.

Separately, depending on the nature of the thing being tested you might consider only running your unit tests in a Node.js runtime. I used to run mine under Node and then again through a browser environment, but have found over the years that the likelihood of the second run catching anything was nearly zero. (Whether this is tenable depends entirely on what you're publishing)

I can understand why this would be confusing, though, since (it's my understanding) most folks compile the raw JS of their npm dependencies down for their webpack bundles. While this will work for testdouble.js, the warnings of passing a string to td.replace would not be thrown and you'll end up in confusing states like this.

cc/ing @jasonkarns for his opinion on what we should do here

@searls
Copy link
Member

searls commented Dec 31, 2017

Closing as it isn't an issue per se, but would be open to changing how the warning works so it doesn't require web users to find dist/testdouble.js

@searls searls closed this as completed Dec 31, 2017
@jasonkarns
Copy link
Member

@fantapop typically, source isn't webpacked prior to running tests, so the test suite (and frameworks) would still have access to the original source tree. Assuming you're webpacking everything down to a single bundle, you wouldn't be able to unit test into that bundle and would only be able to run end-to-end tests of the webpacked bundle. (This is almost universally true regardless of which browser bundler being used, or the test framework.)

The only exceptions to this are if you use webpack to bundle the test suite as well. Thus the tests would be webpacked along with the source, and could run in browser. Though again, td.replace wouldn't work in that webpacked bundle.

No ideas yet on how to improve the error/warning but definitely room for improvement there.

@fantapop
Copy link
Author

Thanks for your replies. Sorry for the confusion, I am using testdouble in node.js. The project is based on this library: https://github.com/serverless-heaven/serverless-webpack.

I was able to work around the webpack issue by separately running babel to convert the es6 module syntax back down to require. I couldn't figure out how to do this with webpack correctly however Its most likely possible.

Regarding the issue I filed, do you not consider this a bug? This code path is still executed in the node js environment.

If I do something like change the name of the module I'm doubling:

const mockDao = testDouble.replace(`../lib/dao`); => const mockDao = testDouble.replace(`../lib/doesntexist`);

It correctly falls through to using the project root to find the module. However, If the module exists, uses only require(), but has an error in it, that error is thrown here but is masked. For example if I interject ```asdfasfdasdf;`` at the top of the file, there error thrown is:

Cannot find module '../lib/dao'

when it should be:

asdfasdfasdf is not defined

An easy fix for this would be something like this in testdouble/src/replace/module.js

var requireAt = (path) => {
  try {
    // 1. Try just following quibble's inferred path
    return require(quibble.absolutify(path))
  } catch (e) {

    if (e.code !== 'MODULE_NOT_FOUND') {
      throw e;
    }
    // 2. Try including npm packages
    return require(resolve.sync(path, { basedir: process.cwd() }))
  }
}

@searls
Copy link
Member

searls commented Dec 31, 2017

I'm sorry, but I'm struggling to follow you. Could you provide a minimal reproducible example or a failing test?

@fantapop
Copy link
Author

@searls
Copy link
Member

searls commented Jan 5, 2018

Wow, thanks for the clear example. I completely understand your point now and this is definitely a bug. Looking at just the stack trace, I want to blame the resolve module, but I can't be sure just yet.

@searls searls reopened this Jan 5, 2018
@searls searls added the bug label Jan 5, 2018
@searls searls changed the title Unparseable lib can return incorrect and misleading error message to user td.replace() on an un-parseable source will claim the module is missing instead of printing the parse error Jan 5, 2018
@searls
Copy link
Member

searls commented Jan 5, 2018

Did a few minutes of research to find that resolve is actually loading the file (this is news to me, as I wasn't aware that Node's path resolution algorithm actually required one to literally read the file), and is blowing up at this line

@searls
Copy link
Member

searls commented Jan 5, 2018

I believe this should now be fixed in td.js and will cut a release to that effect, by removing substack's resolve package and using the built-in require.resolve.

As I dug in, I remembered quibble also depends on the resolve module but can't be so easily replaced, because it needs to preserve symlinks: nodejs/node#18009

Since the purpose of this require call is to get a copy of the real thing, there's no harm in following symlinks (since the required thing will be the same at the link as at the real file), so I think we can safely ship the tdjs fix and wait to hear back about Node.

@searls
Copy link
Member

searls commented Jan 5, 2018

Ok, Landed in 3.3.1 & fixed by 9f51110

Try this and let me know if it works for you

@searls searls closed this as completed Jan 5, 2018
@fantapop
Copy link
Author

fantapop commented Jan 7, 2018

Thanks a ton for working on this. I tested it with my repro and it does the job. I certainly don't understand the nuances of supporting both the browser and node with this code path here but I'm wondering why you didn't want to go with my suggested fix of checking for errors caused by the original include. i.e.

    if (e.code !== 'MODULE_NOT_FOUND') {
      throw e;
    }

The new fix you put into place is actually repeating the work of the first include in order to propagate the error out to the user. You can see this by marking up the dist code with console log statements such as this:

var requireAt = function requireAt(modulePath) {
  try {
    // 1. Try just following quibble's inferred path
    console.log('about to try quibble require');
    var r = require(_quibble2.default.absolutify(modulePath));
    console.log('made it passed quiible require');
    return r;
  } catch (e) {
    // 2. Try including npm packages
    console.log('caught error: ' + e.message);
    console.log('attempting second require');
    return require(require.resolve(modulePath, { paths: [_path2.default.join(process.cwd(), 'node_modules')] }));
  }
};

It produces the following output:

$ node tests/mytest.js
about to try quibble require
caught error: asdfasdf is not defined
attempting second require
/Users/chris/Development/test-double-repro/src/mymodule.js:1
(function (exports, require, module, __filename, __dirname) { asdfasdf;
                                                              ^

ReferenceError: asdfasdf is not defined
    at Object.<anonymous> (/Users/chris/Development/test-double-repro/src/mymodule.js:1:63)
    at Module._compile (module.js:660:30)
    at Object.Module._extensions..js (module.js:671:10)
    at Module.load (module.js:573:32)
    at tryModuleLoad (module.js:513:12)
    at Function.Module._load (module.js:505:3)
    at Module.require (module.js:604:17)
    at require (internal/module.js:11:18)
    at requireAt (/Users/chris/Development/test-double-repro/node_modules/testdouble/lib/replace/module.js:53:12)
    at exports.default (/Users/chris/Development/test-double-repro/node_modules/testdouble/lib/replace/module.js:11:19)

Also, notable is that the comment:
// 2. Try including npm packages is now misleading as you're still trying to require the original module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants