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

Document how to configure Node to allow stubbing EcmaScript modules #1832

Open
fatso83 opened this issue Jun 8, 2018 · 17 comments
Open

Document how to configure Node to allow stubbing EcmaScript modules #1832

fatso83 opened this issue Jun 8, 2018 · 17 comments

Comments

@fatso83
Copy link
Contributor

fatso83 commented Jun 8, 2018

EcmaScript modules that run in an environment supporting them (meaning they haven't been transpiled using Babel to ES5) and export a default function of some kind cannot be stubbed, as ES namespaces are immutable per the spec. There is nothing Sinon can do about this, so we explicitly throw an error when you try to do this: 'ES Modules cannot be stubbed'.

But @jdalton has made esm, a runtime loader for Node that enables loading EcmaScript modules (*.mjs), and to allow stubbing using Sinon and the like, he has added the mutableNamespace option to esm.

There should be an article under our How to section that shows how one can set up npm to execute node using esm and the option, along with a test script.

References:

@fatso83 fatso83 added Documentation ES2015+ ES2015 and later revision labels Jun 8, 2018
@mroderick
Copy link
Member

I've been thinking about writing exactly this :)

@fatso83
Copy link
Contributor Author

fatso83 commented Jun 8, 2018

The "problem" is that there is a plethora of different ways esm can be used, but we should at least cover the most common case, which I would assume is through the require flag to the node process. I started fleshing out how to use the config file here, but it seems it's also possible to supply a json string as a environment variable as well (, in addition to a options hash if using code).

@jdalton
Copy link
Contributor

jdalton commented Jun 11, 2018

Hi @fatso83!

The cjs.mutableNamespace option is enabled by default so there's no configuration necessary. Stubbing will work with .js but not .mjs (.mjs files are locked down, so no esm options).

@fatso83
Copy link
Contributor Author

fatso83 commented Jun 11, 2018

@jdalton Thanks for letting us know of the js vs mjs distinction. That explains why this guy couldn't make it work.

cc @jim-king-2000

@jim-king-2000
Copy link

I'd like to write unit test with the minimum cost. If the solution would be so tricky, I'd rather abandon the unit test with mock. After all, mock test is not the imperative method to build a robust online system. But, as a last resort, is it possible that sinon wraps the "proxyrequire" (or something like this) for me ?

@fatso83
Copy link
Contributor Author

fatso83 commented Jun 11, 2018

@jim-king-2000 That is out of scope. You have chosen explicitly to use a module system whose exports is supposed to be immutable. That is unfortunately a cost you'll have to bear on your own part. Wrapping module loaders, making them work in all kinds of scenarios (Node, browser, with/without transpilers, etc) is too costly and doesn't really have anything to do with this project's stated goals.

@fatso83 fatso83 closed this as completed Jun 11, 2018
@jim-king-2000
Copy link

I don't quite understand the relevance of sinon and module system (I'm sorry). What I need is a js/node mock unit test framework (or library, like the java counterpart, mockito) without babel. So, does it exist?

@fatso83
Copy link
Contributor Author

fatso83 commented Jun 11, 2018

In short, for your specific niche: not currently 😭
In general: yes, there are ways of achieving this for almost any combination of frameworks and runtimes.

In Java terms, it's like implementing your entire system using Java static methods and then trying to mock out the classes using Mockito. It can't be done.

That being said, all you need to make things work is rename your *.mjs files to *.js. This seems like a pragmatic middle way, as you'll gain testability without any known downsides.

@jim-king-2000
Copy link

jim-king-2000 commented Jun 11, 2018

For the Java's static function mocking, we use powermock. But I may not fully understand the comparison. By the way, I don't like java, it's evolution is too slow. Now it still does NOT support async/await.

I use *.mjs everywhere, all of the source codes are mjs files. Further more, it means that I have to resort to babel again (introducing extra dev/run-time work and messy call stack). It is OK if I could only change the test files back to *.js.

I'm going to abandon ut with mock (other tests are intact) until I find some other low-cost ways.

@jim-king-2000
Copy link

@fatso83 Thank you for the help all along.

@bricss
Copy link

bricss commented Dec 1, 2020

Does anyone tried quibble? 🤔

@giltayar
Copy link

FYI, I implemented Node.js ESM support in "testdouble.js", which is a mocking library. It is possible. I wrote about the implementation in this blog post:

https://dev.to/giltayar/mock-all-you-want-supporting-es-modules-in-the-testdouble-js-mocking-library-3gh1

Would be glad to assist here if anybody wants to take it on..

@fatso83
Copy link
Contributor Author

fatso83 commented Jan 18, 2021

@giltayar Congratulations on implementing the ESM support! Great article, btw. We have always said that ES Module stubbing is not possible in conforming ESM Runtimes, but we have also said (like above) that this should be handled at the linking level, using something like proxyquire, rewire, or ... Quibble, which is where you added the support :)

In my work project we have used proxyquire to stub out dependencies from ES Modules:

proxyquire('./mylib.mjs', {doSomething: () => 'done'})

It would be quite equivalent in Quibble (used by TestDouble), where the article has an example like this, but Quibble does not support partial stubs, so it's a bit different in what they do.

await quibble.esm('./mylib.mjs', {doSomething: () => 'done'}, 'yabadabadoing') // not sure what this third param does ...

So in keeping with what has been said earlier, Sinon will never explicitly add support for mocking ES Modules, as that is better left to Quibble, Proxyquire, Rewire, NormalModuleReplacementPlugin (webpack) and all the other ways of doing this that is 100% environment dependent.

@ghost
Copy link

ghost commented Jan 29, 2021

@fatso83 If I may ask why this is such a convinced "never explicitly add support"? I read that multiple times here in the last days while desperately searching for a solution to mock my ES6 module code.

No solutions documented at Jest, none are here. I almost gave up until I found the article of @giltayar. Such a relief. I got something working with quibble until I realized that I can just use testdouble.js.

It is already difficult enough that in JavaScript every package has its own documentation style and most of the time no real API docs, but also having to figure out how test libraries work, mocking libraries work and module loaders for mocking libraries work is just too much.

I totally agree if you say that you focus on Sinon as it is while others can focus on wiring those packages together for the "end user" programmer. I only want to show, that there is real pain for programmers like me and I am sure that many would be happy if the process gets simplified, especially if many will move to ES modules in the next years.

I do not have such a deep technical understanding, I just thought some feedback of my experience could be helpful

@mroderick
Copy link
Member

@fatso83 If I may ask why this is such a convinced "never explicitly add support"?

Let me re-iterate: it is the opinion of the Sinon maintainers that dealing with mocking imports is out of scope for Sinon and is better tackled by specialised libraries.

Generally, it doesn't make sense to make a library that tries to do everything, in every single runtime. Not even bigger, well funded open source projects try to do this.

No solutions documented at Jest, none are here. I almost gave up until I found the article of @giltayar. Such a relief. I got something working with quibble until I realized that I can just use testdouble.js.

Different libraries make different choices.

The maintainers of testdouble.js make their own choices. They decided to publish quibble and integrate it into their library. Good for them. If you like their solution, then by all means use it. We have nothing but love and respect for @searls and the maintainers of testdouble.js.

It is already difficult enough that in JavaScript every package has its own documentation style and most of the time no real API docs, but also having to figure out how test libraries work, mocking libraries work and module loaders for mocking libraries work is just too much.

I totally agree if you say that you focus on Sinon as it is while others can focus on wiring those packages together for the "end user" programmer. I only want to show, that there is real pain for programmers like me and I am sure that many would be happy if the process gets simplified, especially if many will move to ES modules in the next years.

We are not here to solve every single problem in the JavaScript eco-system.

For more than a decade, various maintainers have provided the Sinon family of libraries for free. Practically all of the work that has gone into maintaining these libraries have been done as unpaid work, in the free time of the maintainers. We are using JavaScript ourselves professionally and share your frustrations. But, we only have so much time to give away for free.

I do not have such a deep technical understanding, I just thought some feedback of my experience could be helpful

What would be helpful would be if you wrote a blog post about your frustrations mocking dependencies, until you came upon a solution that worked well for you, and how you've used testdouble.js to great success with your particular way of loading JavaScript.

If it turns out to be a solid blog post, I'd be happy to promote it on sinonjs.org.

@ghost
Copy link

ghost commented Jan 30, 2021

@mroderick I guess I should have first make clear that you and the Sinon maintainers have my upmost respect!

We are not here to solve every single problem in the JavaScript eco-system.

Definitely not, that was just meant to show that there might be a bigger need for help than with other languages (just my guess).

dealing with mocking imports is out of scope for Sinon and is better tackled by specialised libraries.

Fair enough, as I said, I can understand that and probably you have a way deeper understanding of the effort that is involved by implementing those features. Also the loader API is still experimental.

I am currently working on a small CLI tool that I plan to release as open source as soon as there is a working version. If that is done I consider to write a blog post about it. I will still try Sinon with proxyquire before, because I read too many good things about Sinon.

@mroderick
Copy link
Member

I will still try Sinon with proxyquire before, because I read too many good things about Sinon.

We have a guide on how to do that: https://sinonjs.org/how-to/link-seams-commonjs/

If you find that the guide can be improved, please send a pull request 👍

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

No branches or pull requests

7 participants