request/request-promise

Problem with request-promise and request-promise-native in Jest

davazp opened this issue ยท 22 comments

I have recently found that request-promise and request-promise-native can't be required at the same time in Jest. The reason is that Jest messes up with the require.cache implementation badly enough that stealthy-require is not able to return a fresh instance of request.

The second require will fail with the error

  โ— Test suite failed to run

    Unable to expose method "then"

It is quite a problem because both are just indirect dependencies of many projects, so it creates weird incompatibilities.

Can we get rid of the stealthy-require magic and get a different request instance in a different way, to play nicely with Jest module system? For example, could we expose a clone() method to clone request and its prototype instead?

This affects at least to request-promise, request-promise-native and request-promise-any.

I have prepared a minimal repository to reproduce the problem:
https://github.com/davazp/request-promise-issue

If needed, I can work on a pull request for this.

Do you have a temporary solution ?

@ptbrowne Kind of. The problem happened to me between a private project and jsdom. The latter uses request-promise-native.

To work around it, I added "resetModules": true to the jest.config.json file, and then I defined a jsdom.js file in my project with the content:

jest.resetModules()
module.exports = require('jsdom')

Then I require this local file instead of the real jsdom. Unfortunately, jest.resetModules() cleans the cache for every module. I tried to require jsdom at the very last to minimize more issues with other modules.

This should be easy to adapt to other modules you have conflicts with. I hope it helps!

Thanks for the answer. Finally I resorted to jest.mockModule('request-promise-native').

With the new version of Jest, you should use jest.mock('request-promise-native') instead of jest.mockModule

I encountered the same problem, and ended with the options of jest jest.json

"setupFiles": [
    "jest.setup.js"
  ]

and use a file jest.setup.js with

jest.mock('request-promise-native');

I just hit a similar issue when requiring request and request-promise-native. The fix worked for me too.

I am having the same issue, got a private package using request and request-promise-native and the project depends on request-promise-native it throws the error

 FAIL  lib/async/precommission.test.js
  โ— Test suite failed to run

    Unable to expose method "then"

       7 | var _ = require('lodash');
       8 | 
    >  9 | var Update    = require('@privateOrg/update');
         |                 ^
      10 | 
      11 | var dbOptions = {
      12 |   runValidators: true,

      at Object.plumbing.exposePromiseMethod (node_modules/request-promise-core/lib/plumbing.js:140:19)
      at Object.<anonymous>.module.exports (node_modules/request-promise-core/configure/request2.js:57:83)
      at Object.<anonymous> (node_modules/@privateOrg/api/node_modules/request-promise/lib/rp.js:28:1)
      at Object.<anonymous> (node_modules/@privateOrg/api/index.js:4:10)
      at Object.<anonymous> (node_modules/@privateOrg/update/index.js:6:11)
      at Object.require (lib/discovery/handlers/jip/common/index.js:9:17)
      at Object.require (lib/discovery/handlers/jip/fixtures/index.js:9:33)
      at Object.require (lib/discovery/handlers/index.js:4:15)
      at Object.require (lib/models/device.js:6:16)
      at Object.require (lib/async/precommission.js:6:16)
      at Object.require (lib/async/precommission.test.js:1:23)

tried using

jest.mock('request-promise-native');
jest.mock('request-promise');
jest.mock('request-promise-core');

last one throws TypeError: Cannot read property 'exposePromiseMethod' of undefined
I am pretty confused and don't know what to do.

I fixed this just mocking the whole private package, don't know if this is the best approach but it worked for me.

Also, note that this can happen if you have multiple versions of request-promise installed, such as due to differing versions in indirect dependencies. If you are seeing this error and you are not trying to use request-promise and request-promise-native together (I'm only using request-promise) make sure you don't have multiple versions installed.

Has an upstream issue been filed with Jest? It's their module resolver that's broken, seems like they should produce a fix.

Also, note that this can happen if you have multiple versions of request-promise installed, such as due to differing versions in indirect dependencies. If you are seeing this error and you are not trying to use request-promise and request-promise-native together (I'm only using request-promise) make sure you don't have multiple versions installed.

Thanks @mattfff , I noticed in our monorepo we had request-promise in both root node_modules (4.2.2) and one of our package's node_modules (4.2.4). Fixing the sub-package to 4.2.2 fixed the issue for me.

My Jest tests are always failing because of it...

โ— Test suite failed to run

    Unable to expose method "then"

Solution @Benno007 does not fit for me because my function does not work at all if I downgrade it.

Thanks, fixed it like this :

Screen Shot 2019-07-15 at 10 32 50

Also, note that this can happen if you have multiple versions of request-promise installed, such as due to differing versions in indirect dependencies. If you are seeing this error and you are not trying to use request-promise and request-promise-native together (I'm only using request-promise) make sure you don't have multiple versions installed.

Thank @mattfff. I have this issue because I install request-promise and jsdom. I try to use request-promise-native instead of request-promise and this error go away.

I'm also having this problem.. while jest.mock('request-promise'); works for one test case, it breaks some other tests that use the request-promise module. Any long term solutions here?

hems commented

I encountered the same problem, and ended with the options of jest jest.json

"setupFiles": [
    "jest.setup.js"
  ]

and use a file jest.setup.js with

jest.mock('request-promise-native');

That does not solve the issue, at least for me, for instance on a project i'm working we actually let the "post" from "request-promise" happen, as in: we do not mock request-promise.

We let the post or get method actually happen, and then we capture and mock the http responses at "network level" using the powerful https://github.com/nock/nock library, which can record the http requests, create the fixtures, replay the fixtures, debug them, etcs.

It's much more productive and realistically using nock than writing mocked responses and functions for http calls using request-promise.

I've published jest-transform-stealthy-require module to address this issue, the README contains usage examples: https://github.com/antonku/jest-transform-stealthy-require

Hope it helps

hems commented

Hope it helps

this seem to have worked, i can now keep mocking my http requests using https://github.com/nock/nock which i believe is much better than re-writing some methods using sinon an the likes.

EDIT: i can confirm this actually solves things exactly as they are supposed to be solved for us. thank you so much, it saved a lot of headaches on our side. we were stuck working around this very annoying problem for weeks.

i can confirm this actually solves things exactly as they are supposed to be solved for us. thank you so much, it saved a lot of headaches on our side. we were stuck working around this very annoying problem for weeks.

@hems I am happy to hear that it worked out well.
Thank you very much for the feedback!

I've published jest-transform-stealthy-require module to address this issue, the README contains usage examples: https://github.com/antonku/jest-transform-stealthy-require

Hope it helps

Mocking request-promise-native wasn't an option for us as it was a core to the impl under test. This library worked perfectly! Thanks so much mate!

@smber1 Thanks for reaching out! Happy to know it was helpful.

Adding just

jest.mock('request-promise-native');

didn't fix my issue, but adding this did, so maybe it could help someone else too.

jest.mock('request-promise-native', () => ({ options: { request: {} }, }));