Sinon: Document how to configure Node to allow stubbing EcmaScript modules

Created on 8 Jun 2018  ·  17Comments  ·  Source: sinonjs/sinon

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:

Documentation ES2015+ Help wanted good first issue hacktoberfest pinned

Most helpful comment

@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.

All 17 comments

I've been thinking about writing exactly this :)

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).

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)_.

@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

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 ?

@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.

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?

In short, for your specific niche: not currently :sob:
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.

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.

@fatso83 Thank you for the help all along.

Does anyone tried quibble? 🤔

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..

@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.

@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

@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.

@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.

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 👍

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ndhoule picture ndhoule  ·  4Comments

ljian3377 picture ljian3377  ·  3Comments

fearphage picture fearphage  ·  3Comments

tinganho picture tinganho  ·  3Comments

NathanHazout picture NathanHazout  ·  3Comments