Jest: URL behaves differently when run with jest

Created on 15 May 2020  ·  16Comments  ·  Source: facebook/jest

🐛 Bug Report

The object thrown by the constructor of URL is not an instance of Error.

To Reproduce

Create a file error.test.js with the following contents:

#!/usr/bin/env node
try {
  new URL("not_a_url");
} catch (anything) {
  process.exit(anything instanceof Error ? 0 : 1);
}

If you run node error.test.js the program exits successfully. However, if you use jest to run this file you get:

§ jest
  ●  process.exit called with "1"

      3 |   new URL("not_a_url");
      4 | } catch (anything) {
    > 5 |   process.exit(anything instanceof Error ? 0 : 1);
        |           ^
      6 | }
      7 |

      at Object.exit (error.test.js:5:11)

 RUNS  ./error.test.js

Expected behavior

I expect the error raised by the call to URL to be an instance of Error.

envinfo

  System:
    OS: macOS Mojave 10.14.6
    CPU: (8) x64 Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz
  Binaries:
    Node: 12.1.0 - /usr/local/bin/node
    Yarn: 1.22.0 - ~/.yarn/bin/yarn
    npm: 6.13.7 - /usr/local/bin/npm
Bug Report Needs Repro Needs Triage

All 16 comments

Is jest perhaps not using the same implementation of URL as when I run node directly?

Indeed printing out URL.toString() gives different results for the two different invocations.

@fredefox looks like you are correct, whatwg-url is being used (polyfill for URL), but that is not the issue.

Instead, it is related to #2549 (via https://github.com/facebook/jest/issues/6788#issuecomment-409371060).

This does look like it could be a duplicate. Yes. Thanks @carsonreinke. What I'm not really getting is what it has to do with instanceof. I'm not aware that it's possible to override built-in operators. Isn't the problem more that the Jest runtime mutates the global environment.

@fredefox it is really strange, I have not really gotten to the bottom of it, but I did try using https://www.npmjs.com/package/jest-runner-mocha and it does NOT exhibit that behavior.

I am getting TypeError: URL is not a constructor in my jest test, while it works fine in node. Is this perhaps related?

I am getting TypeError: URL is not a constructor in my jest test, while it works fine in node. Is this perhaps related?

I don't think that's related, no.

This is #2549, yeah

@fredefox it is really strange, I have not really gotten to the bottom of it, but I did try using npmjs.com/package/jest-runner-mocha and it does NOT exhibit that behavior.

Yeah, this behavior is specific to Jest since we use the vm module to create sandboxes - mocha runs all the tests in the same context.

Latest update to #2549 is essentially https://github.com/nodejs/node/issues/31852

Thanks @SimenB

@fredefox I believe a good work around for this problem, wherever you are using URL you could specifically import instead of relying on it being globally defined.

@fredefox I believe a good work around for this problem, wherever you are using URL you could specifically import instead of relying on it being globally defined.

I wasn't aware that I could import built-ins. Would you happen to know how you would modify my example to circumvent this? If you don't know off the top of your head it's ok.

@fredefox so I think as simple as adding const URL = require('url'); wherever you are using URL.

Here is an example:

const URL = require('url');

try {
    new URL("not_a_url");
}
catch (anything) {
    console.log(anything instanceof TypeError); //will be true
}

try {
    new global.URL("not_a_url");
}
catch (anything) {
    console.log(anything instanceof TypeError); //will be false
}

I'm not really sure how this would impact the polyfill though, probably nothing since this is a Node/Jest only problem.

However, when I tried it I found that the resulting object is actually not the same, as there is not SearchParameters object

@lukasoppermann Something like const URLSearchParams = require('url').URLSearchParams, if you look at require('url') is actually this:

{
  Url: [Function: Url],
  parse: [Function: urlParse],
  resolve: [Function: urlResolve],
  resolveObject: [Function: urlResolveObject],
  format: [Function: urlFormat],
  URL: [Function: URL],
  URLSearchParams: [Function: URLSearchParams],
  domainToASCII: [Function: domainToASCII],
  domainToUnicode: [Function: domainToUnicode],
  pathToFileURL: [Function: pathToFileURL],
  fileURLToPath: [Function: fileURLToPath]
}

Ah. Thank you very much @carsonreinke! That'll do it.

And re the "not the same" part. Just to expand on what @carsonreinke already wrote. In his example he's shadowing the built-in URL, but the thing doing the shadowing, is _not_ the same as the thing it is shadowing. The thing he is shadowing is present at URL.URL.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

withinboredom picture withinboredom  ·  3Comments

nsand picture nsand  ·  3Comments

kentor picture kentor  ·  3Comments

ianp picture ianp  ·  3Comments

samzhang111 picture samzhang111  ·  3Comments