sinonjs / sinon

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

sandbox.restore() does not work on factory function #2465

Closed NickSun closed 2 years ago

NickSun commented 2 years ago

Describe the bug sandbox.restore() does not work on factory function

To Reproduce Project structure:

.
├── .nvmrc
├── package.json
├── src
│   ├── lib
│   │   ├── index.js
│   │   └── shared.js
│   └── service
│       ├── one.js
│       └── two.js
└── test
    ├── one.spec.js
    └── two.spec.js

./.nvmrc:

18

./package.json:

{
  "name": "sinon.restore",
  "version": "0.0.1",
  "engines": {
    "node": ">=18"
  },
  "scripts": {
    "test": "mocha test/**/*.spec.js"
  },
  "devDependencies": {
    "chai": "^4.3.6",
    "mocha": "^10.0.0",
    "sinon": "^14.0.0"
  }
}

./src/lib/index.js:

'use strict';

const shared = require('./shared');

module.exports = {
  shared
}

./src/lib/shared.js:

'use strict';

module.exports = function() {
  return {
    methodIWantToStub: async () => {
      return Promise.resolve(1);
    }
  }
}

./src/service/one.js:

'use strict';

const sharedIndex = require('../lib/index');

const someFunction = sharedIndex.shared();

function foo() {
  return someFunction.methodIWantToStub();
}

module.exports = {
  foo
};

./src/service/two.js:

'use strict';

const sharedIndex = require('../lib/index');

const someFunction = sharedIndex.shared();

function bar() {
  return someFunction.methodIWantToStub();
}

module.exports = {
  bar
};

./test/one.spec.js

'use strict';

const { expect } = require('chai');
const sinon = require('sinon');

const lib = require('../src/lib/index');

const sandbox = sinon.createSandbox();
const sharedMock = sandbox.stub(lib, 'shared');
sharedMock.returns({ methodIWantToStub: () => Promise.resolve(111) });

const serviceOne = require('../src/service/one');

describe('service one', () => {
  after(()=> {
    sandbox.restore();
  })

  it('should return mocked value 111', (done) => {
    serviceOne.foo()
      .then((value) => {
        expect(value).to.equal(111);
        done();
      })
      .catch(() => done());
  })
})

./test/two.spec.js

'use strict';

const { expect } = require('chai');
const sinon = require('sinon');

const lib = require('../src/lib/index');

const sandbox = sinon.createSandbox();
const sharedMock = sandbox.stub(lib, 'shared');
sharedMock.returns({ methodIWantToStub: () => Promise.resolve(222) });

const serviceTwo = require('../src/service/two');

describe('service two', () => {
  after(()=> {
    sandbox.restore();
  })

  it('should return mocked value 222', (done) => {
    serviceTwo.bar()
      .then((value) => {
        expect(value).to.equal(222);
        done();
      })
      .catch(() => done());
  })
})

npm run test returns error:

TypeError: Attempted to wrap shared which is already wrapped

Expected behavior sandbox.restore() should restore original function behavior

mantoni commented 2 years ago

You're installing the fakes in the module code. Mocha loads all modules before running the tests, therefore the second stub call happens before any tests are executed.

Solution: Install stubs in before or beforeEach hooks.