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

Testdouble third-party-test-thing with ESM module based tests using Mocha #479

Closed
ghinks opened this issue Jan 16, 2022 · 12 comments
Closed

Comments

@ghinks
Copy link

ghinks commented Jan 16, 2022

Testdouble third-party-test-thing with ESM module based tests using Mocha

You can tag this as a Question :)

Scenario

During the course of converting an Opensource module from CJS to ESM I have discarded some tooling. Explicitly

  • babel
  • babel rewire

I have found that the babel rewire tool was very useful for custom mocks of third party modules.

Moving to a fully ESM based solution meant switching mocking tooling. After reading some of @giltayar postings I
started to use Testdouble. It is by all means an excellent tool. However is there an assumption for the third-party-thing-test that when using ESM that the imported third party module is also ESM?

What I believe I am seeing is that for the is-number module frequently used in the testdouble documentation for the third-party-thing-test the case where an ESM module uses a CJS module the replace is not working as documented.

Am I seeing this correctly? If so is it worth me documenting (I'm volunteering) this scenario and saying that one solution is to wrap the third party solution in your own module and to mock that?

I am linking to an example of what I am attempting to describe.

For the example described I am using

  "devDependencies": {
    "chai": "^4.3.4",
    "mocha": "^9.1.4",
    "testdouble": "^3.16.4"
  },
  "dependencies": {
    "is-number": "^7.0.0"
  }

while running node node 16.13.1

@giltayar
Copy link
Collaborator

However is there an assumption for the third-party-thing-test that when using ESM that the imported third party module is also ESM?

Not really. You can use td.replace for CJS and td.replaceEsm for ESM. The slightly non-obvious thing is that you use td.replace for CJS even if you import it.

PR for saying that more explicitly in the documentation I believe would be welcome.

@ghinks
Copy link
Author

ghinks commented Jan 18, 2022

Sincere thanks for the response. I shall conduct some tests of my own and raise a PR to the documentation.

@ghinks ghinks closed this as completed Jan 18, 2022
@ghinks ghinks reopened this Jan 21, 2022
@ghinks
Copy link
Author

ghinks commented Jan 21, 2022

I did try as was suggested however in the scenario that I have linked even with the suggested modifications I'm not convinced that mocking a third party CJS module from a ESM module is working. I"ll update the readme in my example. I am happy to collaborate , update docs or help in any suggested way. If I am using testdouble incorrectly it may be a good case for documentation changes.

@giltayar
Copy link
Collaborator

A reproduction repository would help me help you here!

@ghinks
Copy link
Author

ghinks commented Jan 22, 2022

The link to the repo above has the example https://github.com/ghinks/esm-issues-with-td

@giltayar
Copy link
Collaborator

Nice one!

Fix the problem by adding the line

delete require.cache[require.resolve('is-number')]

before the await import('./index.js').

Explanation: this has nothing to do with ESM. You would have gotten the same result if you'd used CJS for all your files. The problem is specific to is-number, because is-number is used by Mocha as well (to prove it, run npm ls is-number). So when you import 'mocha' at the start of the test file, it's loading is-number into the cache, and thus overriding any attempts to td.replace it. The interesting thing is that if is-number was an ESM module, this would have worked, because ESM replacement is better than CJS replacement in this regard, as detailed in the documentation:

As a result, feel free to use td.replaceEsm even after you import the module. This is different from when using td.replace where you must use td.replace first before require-ing your module. (found in https://github.com/testdouble/testdouble.js/blob/main/docs/7-replacing-dependencies.md#how-module-replacement-works-for-es-modules-using-import)

Hope that was clear enough.

@ghinks
Copy link
Author

ghinks commented Jan 22, 2022

Fantastic a very clear explanation and received with gratitude 🙏🏻

@giltayar
Copy link
Collaborator

@ghinks Two things:

  1. I believe the testdouble explanations are deficient in that area (both for CJS and ESM), and I remember falling through the same trap numerous times. Maybe one day I'll tackle it in the documentation, probably through numerous examples.

  2. If we're done, could you please close the issue? ☺️

@ghinks ghinks closed this as completed Jan 23, 2022
@ghinks ghinks reopened this Jan 23, 2022
@ghinks
Copy link
Author

ghinks commented Jan 23, 2022

Before I close this, please allow me a day to look at what was said and try to implement it. We are all in the ESM boat together and I want to understand and present some documentation changes so others do not have quite the same pain. I'm looking at the NodeJS documentation and while in ESM mode I'll not be able to access require.cache to clear things. So the strategy presented may work for this scenario while working with CJS but while working in ES Modules I"m not sure I see a mechanism to clear loaded modules. Hope, I"m not being too much of a pain. I think we both have the correct intent of clearing a path for future users of the testdouble module.

@giltayar
Copy link
Collaborator

@ghinks. If the package is an ESM package, you won't have the problem: ESM proxying in testdouble handles modules that were already loaded with panache! But I agree the documentation should be clearer on this.

@ghinks
Copy link
Author

ghinks commented Jan 26, 2022

Actually the example is set within package.json as type module. It is not being handled correctly and as it is an ESM module I cannot clear the cache. I will push up more examples. But I believe that this is a real issue.

@giltayar
Copy link
Collaborator

@ghinks the package is-number is a CJS package. That is not dependent on the package.json of the parent package.

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

No branches or pull requests

2 participants