How to use mocha+sinon for unit testing node.js source

How to use mocha+sinon for unit testing node.js source

I think there are cases like testing node.js source with mocha and stubbing it with sinon.

node.jsのソースを単体テストする際のmocha+sinonの使い方

There is English documentation for sinon, and since it is a widely used module, stub methods can be found all over the Internet.

Stubbing methods in sinon

I would like to try sinon with a simple source.

Save the following as index.js

function testMethod(money=0){
  money = money * 1.08;
  return money;
};
exports.sample = testMethod;

Assign testMethod to the variable named exports.sample. This makes the testMethod available to other modules under the name sample.

Let’s stub the return value of this testMethod with sinon.

Here is the index.test.js.

const chai = require('chai');
const sinon = require('sinon');
const assert = chai.assert;
const expect = chai.expect;
const target = require('../../main/sample001/index.js');// require

describe('sinon', function () {
  it('sinonのテスト', function(){
    let stub = sinon.stub(target,'sample');// Stub the target's sample
    let expected = 110;
    stub.returns(expected);// Try to set the return value of SAMPLE to 110.
    let ret = target.sample(50);
    console.log(ret);
    expect(expected).to.equal(ret);
  });
});

Since stub.returns specifies a normal return value (not Promise, etc.), you now have a stub.

If you then execute sample(), the return value will be the specified return value no matter what the argument is.

The result will be as follows

node.jsのソースを単体テストする際のmocha+sinonの使い方

Stubbing methods within methods in sinon

In practice, I think there are many cases of stubbing a method that searches in the DB within a method, for example.

The following is an example of stubbing a method within a method.

function testMethod(money=0){
  let self = this;
  money = money * self.getTax();// Get sales tax from another method
  return money;
};

function getTax() {
  return 1.08;// Image retrieved from DB
}
module.exports = {
  sample: testMethod,
  getTax: getTax
};

I think getTax would normally be retrieved from DB, but since this is a test to see if it can be stubbed, I decided to just return 1.08.

Since this is a test to see if it can be stubbed, we decided to just return 1.08. index.test.js stubs the 1.08 and returns 1.10. The test itself is run from the testMethod method. The test itself is executed from the testMethod method.

const chai = require('chai');
const sinon = require('sinon');
const assert = chai.assert;
const expect = chai.expect;
const target = require('../../main/sample001/index.js');

describe('sinon', function () {
  it('sinon test', function(){
    let stub = sinon.stub(target,'getTax');// Stab.
    let val = 1.10;
    stub.returns(val);// The return value of the getTax method is 1.10
    let ret = target.sample(100);// Input is 100
    let expected = 110;// Expected value is 110
    expect(expected).to.equal(parseInt(ret));
  });
});

Stubbing properties for object keys in sandbox

Returns a sandbox object with the sinon.sandbox.create() method.

*sandbox.create() is deprecated.

Instead, use the require('sinon').createSandbox() method.

sandbox.stub(object name,'key').values('Value to be stubbed')

This description allows you to stub properties for the keys of an object.

The following is an example of execution.

const chai = require('chai')
const sandbox = require('sinon').createSandbox()
const assert = chai.assert
const expect = chai.expect

describe('sinon', function () {
  it('sinon test', function(){
    const myObject = {
      'hello': 'world'
    }
    sandbox.stub(myObject, 'hello').value('Sinon'); // returns is also acceptable in place of value
    let expected = 'Sinon';
    expect(expected).to.equal(myObject.hello);
  })
  afterEach(() => {
    sandbox.restore()
  })
  after(() => {
  })
});

The result is success.

node.jsのソースを単体テストする際のmocha+sinonの使い方

Stubbing the AWS-SDK(CommonJS)

Stub a makeRequest of AWS.Service.prototype. Returns either reject or resolve.

The following is an example of stubbing the confirmSubscription method of SNS.

Test Code

const aws = require('aws-sdk')
~~
~~
const stub = sandbox.stub(AWS.Service.prototype, 'makeRequest')
stub.returns({
  promise: () => {
    return Promise.resolve({'SubscriptionArn':'fuga'})
  }
})

code

const aws = require('aws-sdk')
const sns = new aws.SNS({
  apiVersion: '2010-03-31',
  region: 'ap-northeast-1'
})
~~
~~
const ret = await sns
  .confirmSubscription({
    TopicArn: '',
    Token: ''
  }).promise() // I can mock it up.

Resoleve or reject the return value of Promsie.all.

sandbox.stub(Promise. 'all').resolves()
sandbox.stub(Promise, 'all').rejects()

Here is an example of stubbing the getObject method (static async function) of your own Utility module.

sandbox.stub(Utility, 'getObject').resolves({
"Body": '{ "intercomDeviceId": "00c08f445566" }'
})

Error in sinon

Cannot stub non-existent own property XXX

…This error occurs when there is no method named XXX.

TypeError: Attempted to wrap execute which is already wrapped

…It occurs when you try to stub something that has already been stubbed again.

How to stub process.env (environment variable)

Use sandbox to stub process.env (environment variable).

The createSandbox method creates the sandbox and afterEach restores the sandbox.

Here is the latest documentation on stubs and sandbox.

stubs

sandbox

const sandbox = require('sinon').createSandbox() //sandbox作成

afterEach(()=>{
  sandbox.restore()// Restoring the sandbox
})

after(()=>{
})

Stub environment variables.

sandbox.stub(process.env, 'PATH').value('12345');// Stub the PATH environment variable to 12345

The above will change the value of the PATH environment variable. However, if the environment variable is not set on the PC, the following error will occur

TypeError: Cannot stub non-existent own property PATH

Below is an example of execution.

const chai = require('chai')
const sandbox = require('sinon').createSandbox()
const expect = chai.expect

describe('sinon', function () {
  it('sinon test', function(){
    sandbox.stub(process.env, 'PATH').value('12345')
    expect(process.env.PATH).to.equal('12345') // pass
  })
  
  afterEach(function () {
    sandbox.restore() // Restoring the sandbox
  })
  
  after(function () {
  })
})

Stubbing objects

The sinon.stub method allows only objects to be specified in the first argument.

sinon.stub(target);

In this case, we will stub all methods that target has.

Suppose target has methods aaa,bbb,ccc. In that case, we need to restore() all methods as follows.

beforeEach(function (done) {
  stub = sinon.stub(target);// No method is specified in the second argument.
  done();
});
afterEach(function (done) {
  stub.aaa.restore();
  stub.bbb.restore();
  stub.ccc.restore();
}

Reference Site

Dynamically changing the return value of a method

You can stub the method, but you may want to change the first return value and the second return value.

Use the onCall method as follows

const stub = sinon.stub(target.service, 'getDb');
stub.onCall(0).returns(Promise.resolve('1'));// Return 1 the first time.
stub.onCall(1).returns(Promise.resolve('2'));// Return 2 for the second time.

onCall(0) is the first return value.

onCall(1) is the second return value.

Difference between sandbox.stub().returns, resolves and yields

returns specifies a normal return value.

resolves and rejects specify Promise.

Use yields or callArgsWith for functions with callback functions.

SINON Assertions

The standard assertions are provided in SINON.

sinon.assert.called(index.tax)               // Verification of being called
sinon.assert.calledOnce(index.tax)           // Verification of being called once
sinon.assert.notCalled(index.tax)            // Verification of not being called
sinon.assert.calledTwice(index.tax)          // Verification of being called twice
sinon.assert.callOrder(index.tax, index.age) // Verification of the order in which stubbed methods are called
sinon.assert.callCount(index.tax, 3)         // Verification of the number of times a stubbed method has been called

This is a sandbox assertion, verifying how many times Promise.all is called.

sandbox.stub(Promise, 'all').resolves()
// processing
sandbox.assert.calledOnce(Promise.all)

calledOnce, calledTwice, calledThrice, etc. are available.

Sandboxes - Sinon.JS
Sandboxes removes the need to keep track of every fake created, which greatly simplifies cleanup.

Return value memo

Example of return value of listObjectsV2 method

const listObjectsV2Ret = {
  IsTruncated: false,
  Contents: [
    {
      Key: 'var/tmp/a.json',
      last_modified: '2020-07-07T09:40:57.000Z',
      etag: '"14758w1afd44c09b7456167wwa00b43d"',
      size: 24,
      storage_class: 'STANDARD'
   }
  ],
  Name: 'bucket',
  Prefix: 'var/tmp/',
  MaxKeys: 1000,
  KeyCount: 1
}

Mocking a class that is new

How to mock a class.

code

module.exports.Index = class Index {
  constructor() {}

  async _get() {
    // ~~
  }
}

Test Code

const Index = require('../src/index').Index
const sandbox = require('sinon').createSandbox()

describe('', async () => {
  it('_get method test',async () => {
    sandbox.stub(Index.prototype, '_get').callsFake(() => {
      return {}
    })
    // ~~
  })

})

コメント

Discover more from 株式会社CONFRAGE ITソリューション事業部

Subscribe now to keep reading and get access to the full archive.

Continue reading

Copied title and URL