Sinon error Attempted to wrap function which is al

2020-05-18 04:47发布

问题:

Though there is a same question here but I could not find answer to my problem so here goes my question:

I am testing my node js app using mocha and chai. I am using sinion to wrap my function.

describe('App Functions', function(){

  let mockObj = sinon.stub(testApp, 'getObj', (dbUrl) => {
     //some stuff
  });
  it('get results',function(done) {
     testApp.someFun
  });
}

describe('App Errors', function(){

  let mockObj = sinon.stub(testApp, 'getObj', (dbUrl) => {
     //some stuff
  });
  it('throws errors',function(done) {
     testApp.someFun
  });
}

When I try to run this test it gives me error

Attempted to wrap getObj which is already wrapped

I also tried putting

beforeEach(function () {
  sandbox = sinon.sandbox.create();
});

afterEach(function () {
  sandbox.restore();
});

in each describe, but still giving me same error.

回答1:

You should restore the getObj in after() function, please try it as below.

describe('App Functions', function(){
    var mockObj;
    before(function () {
            mockObj = sinon.stub(testApp, 'getObj', () => {
                 console.log('this is sinon test 1111');
            });
    });

    after(function () {
        testApp.getObj.restore(); // Unwraps the spy
    });

    it('get results',function(done) {
        testApp.getObj();
    });
});

describe('App Errors', function(){
    var mockObj;
    before(function () {
            mockObj = sinon.stub(testApp, 'getObj', () => {
                 console.log('this is sinon test 1111');
            });
    });

    after( function () {
        testApp.getObj.restore(); // Unwraps the spy
    });

    it('throws errors',function(done) {
         testApp.getObj();
    });
});


回答2:

This error is due to not restoring the stub function properly. Use sandbox and then create the stub using the sandbox. After the test suite restore the sandbox.

  before(() => {
      sandbox         = sinon.createSandbox();
      mockObj         = sandbox.stub(testApp, 'getObj', fake_function)
  });
  after(() => {
      sandbox.restore();
  });


回答3:

For cases where you need to restore all the methods of one object, you can use the sinon.restore(obj).

Example:

before(() => {
    userRepositoryMock = sinon.stub(userRepository);
});

after(() => {
    sinon.restore(userRepository);
});


回答4:

I was also hitting this using the before() and after() hooks of Mocha. I was also using the restore() as mentioned everywhere. Single test file ran fine, multiple did not. Finally found about Mocha root-level-hooks: I did not have my before() and after() inside my own describe(). So it finds all files with before() at the root-level and executes those before starting any tests.

So make sure you have a similar pattern:

describe('my own describe', () => {
  before(() => {
    // setup stub code here
    sinon.stub(myObj, 'myFunc').callsFake(() => {
      return 'bla';
    });
  });
  after(() => {
    myObj.myFunc.restore();
  });
  it('Do some testing now', () => {
    expect(myObj.myFunc()).to.be.equal('bla');
  });
});


回答5:

It is advised to initialize stubs in 'beforeEach' and restore them in 'afterEach'. But in case you are feeling adventurous, the following works too.

describe('App Functions', function(){

  let mockObj = sinon.stub(testApp, 'getObj', (dbUrl) => {
     //some stuff
  });
  it('get results',function(done) {
     testApp.someFun
     mockObj .restore();
  });
}

describe('App Errors', function(){

  let mockObj = sinon.stub(testApp, 'getObj', (dbUrl) => {
     //some stuff
  });
  it('throws errors',function(done) {
     testApp.someFun
     mockObj .restore();
  });
}


回答6:

Even with sandbox it could give you the error. Especially when tests run in parallel for ES6 classes.

const sb = sandbox.create();

before(() => {
  sb.stub(MyObj.prototype, 'myFunc').callsFake(() => {
    return 'whatever';
  });
});
after(() => {
  sb.restore();
});

this could throw the same error if another test is trying to stub myFunc from the Prototype. I was able to fix that but I am not proud of it...

const sb = sandbox.create();

before(() => {
  MyObj.prototype.myFunc = sb.stub().callsFake(() => {
    return 'whatever';
  });
});
after(() => {
  sb.restore();
});


回答7:

For anyone running into this issue, if you stub or spy on the entire object, and you later do

sandbox.restore()

You'll still get the error. You have to stub/spy the individual methods.

I wasted forever trying to figure out what was wrong.

sinon-7.5.0



回答8:

I ran into this with spies. This behaviour makes sinon pretty inflexible to work with. I created a helper function that attempts to remove any existing spy before setting a new one. That way I don't have to worry about any before/after state. A similar approach might work for stubs too.

import sinon, { SinonSpy } from 'sinon';

/**
 * When you set a spy on a method that already had one set in a previous test,
 * sinon throws an "Attempted to wrap [function] which is already wrapped" error
 * rather than replacing the existing spy. This helper function does exactly that.
 *
 * @param {object} obj
 * @param {string} method
 */
export const spy = function spy<T>(obj: T, method: keyof T): SinonSpy {
  // try to remove any existing spy in case it exists
  try {
    // @ts-ignore
    obj[method].restore();
  } catch (e) {
    // noop
  }
  return sinon.spy(obj, method);
};



标签: node.js sinon