sinonjs / sinon

Test spies, stubs and mocks for JavaScript.
https://sinonjs.org/
Other
9.63k stars 769 forks source link

AssertError: async function myfunct() ... is not stubbed #2478

Closed GeoffreyPlitt closed 1 year ago

GeoffreyPlitt commented 1 year ago

I'm getting "async function" ... "is not stubbed" errors on sinon test code that used to work fine.

We have a module that has an exported function, i.e. mymodule.js has

export async function myFunction() {
...
}

The test in question stubs a module-level exported function...

sinon.stub(myModule, 'myFunction');

then calls it, and then we do

sinon.assert.calledOnce(myModule.myFunction)

and we get the error above.

It used to work, but we've migrated our project from yarn to pnpm and some sinon-related packages drifted, but it's hard to figure out what's wrong.

Any idea why a module-level exported function which is stubbed, would then give "is not stubbed" shortly after?

fatso83 commented 1 year ago

Any idea why a module-level exported function which is stubbed, would then give "is not stubbed" shortly after?

I do not, sorry.

we've migrated our project from yarn to pnpm

That is very unlikely to have had an effect. I would rather bet on moving from Webpack 3 to Webpack 5 or Vite or something like that to be the cause of such changes.

The code in question is this

image

It basically says that the exported function has not successfully been replaced with a stub. That makes sense from the point of an ES2015+ compliant environment: exported functions of a module cannot be replaced, as the namespace is immutable per spec. We even have a unit test to make sure we catch these before the runtime throws.

What is strange is that nothing has failed before the assert call runs. Which makes me assume that you are running in some kind of transpiled environment where "normal ES Module rules" do not apply. Is that the case? For instance Webpack 4/5 transpiles ESM to modules that utilize read-only object descriptors with getters for the exported fields. Which means your myFunction would probably look something like

Object.defineProperty(module.exports, 'myFunction',   { get: () =>  (...args) =>  doSomething(args) } )
// The field has `writable : false` by default
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

This would silently fail if we tried replacing that field.

As you say, it is a hard to reason about this without a reproducible case ... Have you tried console.loging myModule and/or play with it in a debugger? It would be interesting to see if you would be able to do myModule.myFunction = 42 for instance or what the results of Object.getOwnPropertyDescriptor would be. That would probably shed some light on what is happening.

fatso83 commented 1 year ago

Closing as no feedback.