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

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

node.jsのソースをmochaでテストしているとsinonでスタブしてテストする、といったケースがあると思います。

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

sinonは英語ドキュメントもありますし、広く使われているモジュールなのでスタブの方法がネットにごろごろ転がっています。

sinonでメソッドをスタブする

簡単なソースでsinonを試してみたいと思います。

以下をindex.jsで保存します。

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

exports.sampleという変数にtestMethodを代入します。これでtestMethodをsampleという名前で他のモジュールからも使えるようにします。

このtestMethodの戻り値をsinonでスタブしてみます。

以下、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');// targetのsampleをスタブする
    let expected = 110;
    stub.returns(expected);// sampleの戻り値は110にしてみる
    let ret = target.sample(50);
    console.log(ret);
    expect(expected).to.equal(ret);
  });
});

stub.returnsで通常の戻り値(Promiseなどではない)を指定しているので、これでスタブができたことになります。

そのあとにsample()を実行すると引数が何であっても戻り値は指定した戻り値となります。

結果は以下のようになります。

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

sinonでメソッド内のメソッドをスタブする

実際はメソッド内にあるDBに検索するメソッドなどをスタブするというケースが多いかと思います。

以下はメソッド内のメソッドをスタブする例です。

function testMethod(money=0){
  let self = this;
  money = money * self.getTax();// 消費税を別のメソッドから取得
  return money;
};

function getTax() {
  return 1.08;// DBから取得しているイメージ
}
module.exports = {
  sample: testMethod,
  getTax: getTax
};

getTaxは通常はDBから取得すると思いますが、スタブできるかのテストなので1.08を返すだけにしました。

index.test.jsで1.08を返すのをスタブ化し、1.10を返すようにしています。テスト自体はtestMethodメソッドから実行しています。

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のテスト', function(){
    let stub = sinon.stub(target,'getTax');// スタブする
    let val = 1.10;
    stub.returns(val);// getTaxメソッドの戻り値は1.10
    let ret = target.sample(100);// インプットは100
    let expected = 110;// 期待値は110
    expect(expected).to.equal(parseInt(ret));
  });
});

sandboxでオブジェクトのキーに対するプロパティをスタブする

sinon.sandbox.create()メソッドでsandboxオブジェクトを返します。

※sandbox.create()は非推奨となっています

変わりにrequire('sinon').createSandbox()メソッドを使用します。

sandbox.stub(オブジェクト,'キー').values('スタブしたい値')

このように記述するとオブジェクトのキーに対するプロパティをスタブすることができます。

以下、実行例です。

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

describe('sinon', function () {
  it('sinonのテスト', function(){
    const myObject = {
      'hello': 'world'
    }
    sandbox.stub(myObject, 'hello').value('Sinon'); // valueの代わりにreturnsも可
    let expected = 'Sinon';
    expect(expected).to.equal(myObject.hello);
  })
  afterEach(() => {
    sandbox.restore()
  })
  after(() => {
  })
});

結果は成功します。

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

AWS-SDKをスタブする

AWS.Service.prototypeのmakeRequestをスタブします。返すのはrejectするかresolveするかです。

以下はSNSのconfirmSubscriptionメソッドをスタブした例です。

テストコード

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

ソース

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() // モックできる

Promsie.allの戻り値をresoleveまたはrejectします。

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

自作のUtilityモジュールのgetObjectメソッド(static async function)をスタブする例です。

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

sinonのエラー

Cannot stub non-existent own property XXX

…XXXというメソッドがない場合にこのエラーが発生する。

TypeError: Attempted to wrap execute which is already wrapped

…すでにスタブしているものをもう一度スタブしようとすると発生する。

sinonの評価はmocha+chaiの使い方を参照して下さい。

process.env(環境変数)をスタブする方法

process.env(環境変数)をスタブするにはsandboxを使用します。

createSandboxメソッドでsandboxを作成し、afterEachでサンドボックスのrestoreします。

stubsとsandboxの最新ドキュメントです。

stubs

sandbox

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

afterEach(()=>{
  sandbox.restore()// sandboxをリストアすること
})

after(()=>{
})

環境変数をスタブします。

sandbox.stub(process.env, 'PATH').value('12345');// 環境変数PATHを12345にスタブ

上記で環境変数PATHの値を変えることができます。但し、環境変数がPCに設定されていない場合は以下のようなエラーとなります。

TypeError: Cannot stub non-existent own property PATH

以下、実行例です。

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

describe('sinon', function () {
  it('sinonのテスト', function(){
    sandbox.stub(process.env, 'PATH').value('12345')
    expect(process.env.PATH).to.equal('12345') // passになる
  })
  
  afterEach(function () {
    sandbox.restore() // sandboxをリストアすること
  })
  
  after(function () {
  })
})

オブジェクトをスタブする

sinon.stubメソッドは第一引数でオブジェクトだけを指定することができます。

sinon.stub(target);

この場合、targetが持つすべてのメソッドをスタブすることになります。

targetはaaa,bbb,cccと言うメソッドを持つとします。その場合、以下のように全てのメソッドをrestore()する必要があります。

beforeEach(function (done) {
  stub = sinon.stub(target);// 第二引数でメソッドは指定しない
  done();
});
afterEach(function (done) {
  stub.aaa.restore();
  stub.bbb.restore();
  stub.ccc.restore();
}

参考サイト

メソッドの戻り値を動的に変更する

メソッドをスタブすることができますが、1回目の戻り値と2回目の戻り値を変更したい場合があります。

以下のようにonCallメソッドを使用します。

const stub = sinon.stub(target.service, 'getDb');
stub.onCall(0).returns(Promise.resolve('1'));// 1回目は1を返す
stub.onCall(1).returns(Promise.resolve('2'));// 2回目は2を返す

onCall(0)が1回目の戻り値になります。

onCall(1)が2回目の戻り値になります。

sandbox.stub().returnsとresolvesとyieldsの違い

returnsは通常の戻り値を指定します。

resolvesrejectsはPromiseを指定します。

コールバック関数を持つ関数には yieldsまたはcallArgsWithを使用します。

sinonのアサーション

sinonには標準でアサーションが用意されています。

sinon.assert.called(index.tax)               // 呼ばれたことの検証
sinon.assert.calledOnce(index.tax)           // 1回呼ばれたことの検証
sinon.assert.notCalled(index.tax)            // 呼ばれていないことの検証
sinon.assert.calledTwice(index.tax)          // 2回呼ばれたことの検証
sinon.assert.callOrder(index.tax, index.age) // スタブしたメソッドの呼ばれた順序の検証
sinon.assert.callCount(index.tax, 3)         // スタブしたメソッドの呼ばれた回数の検証

sandboxのアサーションです。Promise.allが何回呼ばれたかを検証しています。

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

calledOnce,calledTwice,calledThriceなどが用意されています。

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

戻り値メモ

listObjectsV2メソッドの戻り値例

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
}

newしているクラスをモックする

クラスをモックする方法です。

ソース

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

  async _get() {
    // ~~
  }
}

テストコード

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

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

})

コメント

株式会社CONFRAGE ITソリューション事業部をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む

タイトルとURLをコピーしました