-
-
Notifications
You must be signed in to change notification settings - Fork 769
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
Bug mocking with SWC #2528
Comments
Hi and thanks for making a good reproduction case! Great to have a simple reproduction repository. Sinon makes mocks, stubs and spies. Those are plain, normal functions, and we do not do any magic to the runtime, so whatever is to be done needs to be done using normal Javascript in the given runtime. Sinon clearly states what the issue is:
If the transpiled code restricts everyone from modifying those exports, Sinon cannot do anything by itself. This issue is not an issue with Sinon, but with your transpiler tooling. While ts-node clearly exports a writeable object descriptor, SWC does not. This is in-line with how ES Modules are supposed to work, so SWC seems to be the correct behavior here. Just googling "swc writable object descriptor" comes up with a few suggestions, some ok workarounds. I would still try another approach, if you just need to do this to have mutable namespaces for your modules, and that is to use the Dalton's ESM loader. It has an option to make namespaces mutable and you can register it before your SWC module. The option in ESM:
|
I've tried the suggestion in stackoverflow as I also found this before but it's not working either. |
You can use mocks in the original sense, but nowadays mocking is more colloquially used as a term for intercepting loading of dependencies and replacing those with some test double (stub, fake, mock, dummy). That was easier when "everyone" was using derivatives of the CommonJS module system, either in Node or using some bundler like Browserify or Webpack. This was because the importing of other modules happened at runtime, so you could SolutionsReplace module loaderOnce you are in true ESM land (not transpiled to ES5 and/or CJS), you cannot mutate exports in the normal runtime. For that to be allowed, you need to hook into the process earlier, by replacing the module loader. This is actually been allowed in Node for ages (Node 13?) and used by various tools. One alternative to Sinon is TestDouble, which has most of the basic functionality of Sinon, but it also bundles its own module loader for use with ESM! You can either go all in on that, or just use the module loading. Here's the link for documentation on how to use this (it's quite painless): td.replaceEsm()
then await quibble.esm('./a-module.mjs', {life: 41}, 'replacement universe'); Check the docs. P.S. By now, I actually cannot remember if using SWC results actual ES Modules (which have immutable namespace) or something else (seeing the object descriptor thingies above. Sorry. I have the memory of a goldfish), so this might or might not work for your 🤷 If it turns out the resulting transpiled code is not ESM you can just use Alternative to hooking into the link layer: pure dependency injectionPure dependency injection (previously called "poor man's DI") is something I often reach for to avoid complicating my setup. You get a slight change in production code to enable better tests. You can see my describing a fuller example of this in #831 (comment) Issues related to ESM stubbing: #831, #1623, #1711, |
P.S. I have some extra time today, so thought I might as well try getting this working. Testing out Quibble now. |
Working fine! I forked your repo and got it working: https://github.com/fatso83/sinon-swc-bug/tree/cjs-mocking Be aware, that this is relying on CommonJS modules. At first, I configured Quibble to do ESM replacements, but I was not able to get it working. The problem was that this was never ESM to begin with. Your it("should mock", () => {
const mocked = sandbox.fake.returns("mocked");
quibble("./other.ts", { toBeMocked: mocked });
const {main} = require("./main");
main();
expect(mocked.called).to.be.true;
}); P.S. This is the first time I have used Quibble (TestDouble). You could just as well have used Proxyquire, which is what we suggest in our how-to for replacing modules. |
Another approach is of course to just modify the output of SWC to make the exports mutable (writable object descriptors). The The issue with non-mutable exports is the same for Webpack and other transpilers, for that matter. |
Some notesI looked into how to configure a TS project into using an ESM (not-CommonJS module transpilation) based approach and ran into one problem: when configuring TypeScript to produce ES Modules, you need to specify file extensions in your imports (not a problem). You also need to make Node aware how the As you can currently only specify a single loader, you would then need to implement a custom loader that did the juggling of making the output of So if using SWC, I would configure your TS project to use CJS as the module target for the time being (which is the default!), if possible and just use the approach I showed above (in the If you do not need TypeScript and just need to generally stub ESM, I put a working ESM example in the https://github.com/fatso83/sinon-swc-bug/tree/cjs-mocking |
@assertnotnull Did you see the fix/workaround I provided for you? Was just wondering if that worked fine, as I was thinking of updating the old link seams article with examples for modern bundlers/transpilers, as this comes up quite a bit. |
I was also thinking that it might be useful to have error messages point directly to relevant docs? So instead of "Descriptor for property toBeMocked is non-configurable and non-writable" change it to something like
(The link is made up by the way). |
OK, I have found yet another option that works: the SWC plugin that is confusingly called https://github.com/fatso83/sinon-swc-bug/tree/swc-with-mutable-exports The issue is that the exports of There is also another option I am working on. In any case, #2534 needs to fixed as well, though it's not blocking (as you can manually restore the accessor stubs). I will try to gather all of these options in an article on the Sinon documentation page, if time be willing. |
Thank you @fatso83 for spending much time looking into this. I find this is quite complicated to use different loaders, specially if such change was applied to a big project but the last solution is interesting. I will give it a try. |
Good to hear from you! I did write that article in the end, btw, but no-one has commented and reviewed my PR. I should contain all the details one could need. Would you have a chance at reviewing it to see if this looks useful for people? It's #2540. I attached/linked the article as PDF for easy reading |
The changes https://github.com/fatso83/sinon-swc-bug/tree/swc-with-mutable-exports works! |
I must add, this is in a Nestjs project where Jest has been replaced with Mocha + Sinon. We faced memory leaks with Jest in the past. |
Describe the bug
Sinon is unable to mock the function when compiled with SWC but works with ts-node
To Reproduce
clone the sample repo with bug
Outcome:
To see it working with ts-node replace "@swc/register" with "ts-node/register"
Then run pnpm test again and it passes.
Expected behavior
It should mock
The text was updated successfully, but these errors were encountered: