如何使用Sinon对单元测试节点API(Express with Mongo DB)

时间:2023-01-03 02:57:41

I am creating an API using Node but am struggling to understand how to properly Unit test the API. The API itself uses Express and Mongo (with Mongoose).

我正在使用Node创建API,但我很难理解如何正确地对API进行单元测试。 API本身使用Express和Mongo(使用Mongoose)。

So far I have been able to create Integration tests for end to end testing of the API endpoints themselves. I have used supertest, mocha and chai for the integration tests along with dotenv to use a test database when running it. The npm test script sets the environment to test before the integration tests run. It works excellently.

到目前为止,我已经能够为API端点本身的端到端测试创建集成测试。我使用supertest,mocha和chai进行集成测试以及dotenv在运行时使用测试数据库。 npm测试脚本在集成测试运行之前设置要测试的环境。它工作得很好。

But I would like to also create Unit Tests for various components such as the controller functions.

但我还想为各种组件创建单元测试,例如控制器功能。

I'm keen to use Sinon for the Unit Tests but I'm struggling to know what next steps to take.

我很想将Sinon用于单元测试,但我很难知道下一步要采取什么措施。

I'll detail a genericised version of the API rewritten to be everybody's favourite Todos.

我将详细介绍API的通用版本,重写为每个人最喜欢的Todos。

The app has the following directory structure:

该应用程序具有以下目录结构:

api
|- todo
|   |- controller.js
|   |- model.js
|   |- routes.js
|   |- serializer.js
|- test
|   |- integration
|   |  |- todos.js
|   |- unit
|   |  |- todos.js
|- index.js
|- package.json

package.json

的package.json

{
  "name": "todos",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "directories": {
    "doc": "docs"
  },
  "scripts": {
    "test": "mocha test/unit --recursive",
    "test-int": "NODE_ENV=test mocha test/integration --recursive"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.15.0",
    "express": "^4.13.4",
    "jsonapi-serializer": "^3.1.0",
    "mongoose": "^4.4.13"
  },
  "devDependencies": {
    "chai": "^3.5.0",
    "mocha": "^2.4.5",
    "sinon": "^1.17.4",
    "sinon-as-promised": "^4.0.0",
    "sinon-mongoose": "^1.2.1",
    "supertest": "^1.2.0"
  }
}

index.js

index.js

var express = require('express');
var app = express();
var mongoose = require('mongoose');
var bodyParser = require('body-parser');

// Configs
// I really use 'dotenv' package to set config based on environment.
// removed and defaults put in place for brevity
process.env.NODE_ENV = process.env.NODE_ENV || 'development';

// Database
mongoose.connect('mongodb://localhost/todosapi');

//Middleware
app.set('port', 3000);
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Routers
var todosRouter = require('./api/todos/routes');
app.use('/todos', todosRouter);

app.listen(app.get('port'), function() {
    console.log('App now running on http://localhost:' +     app.get('port'));
});

module.exports = app;

serializer.js

serializer.js

(This purely takes the output from Mongo and serializes it into JsonAPI format. So it is a bit superfluous to this example but I left it in as it is something I currently make use of in the api.)

(这纯粹是从Mongo输出并将其序列化为JsonAPI格式。所以这个例子有点多余,但我把它留在了,因为它是我目前在api中使用的东西。)

'use strict';

var JSONAPISerializer = require('jsonapi-serializer').Serializer;

module.exports = new JSONAPISerializer('todos', {
    attributes: ['title', '_user']
    ,
    _user: {
        ref: 'id',
        attributes: ['username']
    }
});

routes.js

routes.js

var router = require('express').Router();
var controller = require('./controller');

router.route('/')
    .get(controller.getAll)
    .post(controller.create);

router.route('/:id')
    .get(controller.getOne)
    .put(controller.update)
    .delete(controller.delete);

module.exports = router;

model.js

model.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var todoSchema = new Schema({
    title: {
        type: String
    },

    _user: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    }
});

module.exports = mongoose.model('Todo', todoSchema);

controller.js

controller.js

var Todo = require('./model');
var TodoSerializer = require('./serializer');

module.exports = {
    getAll: function(req, res, next) {
        Todo.find({})
            .populate('_user', '-password')
            .then(function(data) {
                var todoJson = TodoSerializer.serialize(data);
                res.json(todoJson);
            }, function(err) {
                next(err);
            });
    },

    getOne: function(req, res, next) {
        // I use passport for handling User authentication so assume the user._id is set at this point
        Todo.findOne({'_id': req.params.id, '_user': req.user._id})
            .populate('_user', '-password')
            .then(function(todo) {
                if (!todo) {
                    next(new Error('No todo item found.'));
                } else {
                    var todoJson = TodoSerializer.serialize(todo);
                    return res.json(todoJson);
                }
            }, function(err) {
                next(err);
            });
    },

    create: function(req, res, next) {
        // ...
    },

    update: function(req, res, next) {
        // ...
    },

    delete: function(req, res, next) {
        // ...
    }
};

test/unit/todos.js

测试/单元/ todos.js

var mocha = require('mocha');
var sinon = require('sinon');
require('sinon-as-promised');
require('sinon-mongoose');
var expect = require('chai').expect;
var app = require('../../index');

var TodosModel = require('../../api/todos/model');

describe('Routes: Todos', function() {
  it('getAllTodos', function (done) {
    // What goes here?
  });

  it('getOneTodoForUser', function (done) {
      // What goes here?
  });
});

Now I don't want to test the routes themselves (I do that in the Integration Tests not detailed here).

现在我不想自己测试路由(我在这里没有详细介绍的集成测试中这样做)。

My current thinking is that the next best thing is to actually unit test controller.getAll or controller.getOne functions. And then to Mock the calls to Mongo via Mongoose using Sinon stubs.

我目前的想法是,下一个最好的事情是实际单元测试controller.getAll或controller.getOne函数。然后使用Sinon存根通过Mongoose模拟对Mongo的调用。

But I have no idea what to do next despite having read the sinon docs :/

但是,尽管已经阅读过sinon文档,但我不知道下一步该做什么:/

Questions

问题

  • How do I test controller functions if it requires req, res, next as parameters?
  • 如果需要req,res,next作为参数,如何测试控制器功能?
  • Do I move the model's find and populate (currently in the Controller function) into todoSchema.static functions?
  • 我是否将模型的查找和填充(当前在Controller函数中)移动到todoSchema.static函数中?
  • How to mock the populate function to do a Mongoose JOIN?
  • 如何模拟populate函数来做一个Mongoose JOIN?
  • Basically what goes into test/unit/todos.js to get the above in a solid Unit Test state :/
  • 基本上是什么进入test / unit / todos.js以获得上面的固定单元测试状态:/

The end goal is to run mocha test/unit and have it unit test the various parts of that API section

最终目标是运行mocha测试/单元并让它对该API部分的各个部分进行单元测试

1 个解决方案

#1


2  

Hi I've created some test for you to understand how to use mocks.

嗨,我已经为您创建了一些测试,以了解如何使用模拟。

Full example github/nodejs_unit_tests_example

完整示例github / nodejs_unit_tests_example

controller.test.js

controller.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')
const assert = require('chai').assert

describe('todo/controller', () => {
  describe('controller', () => {

    let mdl
    let modelStub, serializerStub, populateMethodStub, fakeData
    let fakeSerializedData, fakeError
    let mongoResponse

    before(() => {
      fakeData = faker.helpers.createTransaction()
      fakeError = faker.lorem.word()
      populateMethodStub = {
        populate: sinon.stub().callsFake(() => mongoResponse)
      }
      modelStub = {
        find: sinon.stub().callsFake(() => {
          return populateMethodStub
        }),
        findOne: sinon.stub().callsFake(() => {
          return populateMethodStub
        })
      }

      fakeSerializedData = faker.helpers.createTransaction()
      serializerStub = {
        serialize: sinon.stub().callsFake(() => {
          return fakeSerializedData
        })
      }

      mdl = proxyquire('../todo/controller.js',
        {
          './model': modelStub,
          './serializer': serializerStub
        }
      )
    })

    beforeEach(() => {
      modelStub.find.resetHistory()
      modelStub.findOne.resetHistory()
      populateMethodStub.populate.resetHistory()
      serializerStub.serialize.resetHistory()
    })

    describe('getAll', () => {
      it('should return serialized search result from mongodb', (done) => {
        let resolveFn
        let fakeCallback = new Promise((res, rej) => {
          resolveFn = res
        })
        mongoResponse = Promise.resolve(fakeData)
        let fakeRes = {
          json: sinon.stub().callsFake(() => {
            resolveFn()
          })
        }
        mdl.getAll(null, fakeRes, null)

        fakeCallback.then(() => {
          sinon.assert.calledOnce(modelStub.find)
          sinon.assert.calledWith(modelStub.find, {})

          sinon.assert.calledOnce(populateMethodStub.populate)
          sinon.assert.calledWith(populateMethodStub.populate, '_user', '-password')

          sinon.assert.calledOnce(serializerStub.serialize)
          sinon.assert.calledWith(serializerStub.serialize, fakeData)

          sinon.assert.calledOnce(fakeRes.json)
          sinon.assert.calledWith(fakeRes.json, fakeSerializedData)
          done()
        }).catch(done)
      })

      it('should call next callback if mongo db return exception', (done) => {
        let fakeCallback = (err) => {
          assert.equal(fakeError, err)
          done()
        }
        mongoResponse = Promise.reject(fakeError)
        let fakeRes = sinon.mock()
        mdl.getAll(null, fakeRes, fakeCallback)
      })

    })

    describe('getOne', () => {

      it('should return serialized search result from mongodb', (done) => {
        let resolveFn
        let fakeCallback = new Promise((res, rej) => {
          resolveFn = res
        })
        mongoResponse = Promise.resolve(fakeData)
        let fakeRes = {
          json: sinon.stub().callsFake(() => {
            resolveFn()
          })
        }

        let fakeReq = {
          params: {
            id: faker.random.number()
          },
          user: {
            _id: faker.random.number()
          }
        }
        let findParams = {
          '_id': fakeReq.params.id,
          '_user': fakeReq.user._id
        }
        mdl.getOne(fakeReq, fakeRes, null)

        fakeCallback.then(() => {
          sinon.assert.calledOnce(modelStub.findOne)
          sinon.assert.calledWith(modelStub.findOne, findParams)

          sinon.assert.calledOnce(populateMethodStub.populate)
          sinon.assert.calledWith(populateMethodStub.populate, '_user', '-password')

          sinon.assert.calledOnce(serializerStub.serialize)
          sinon.assert.calledWith(serializerStub.serialize, fakeData)

          sinon.assert.calledOnce(fakeRes.json)
          sinon.assert.calledWith(fakeRes.json, fakeSerializedData)
          done()
        }).catch(done)
      })

      it('should call next callback if mongodb return exception', (done) => {
        let fakeReq = {
          params: {
            id: faker.random.number()
          },
          user: {
            _id: faker.random.number()
          }
        }
        let fakeCallback = (err) => {
          assert.equal(fakeError, err)
          done()
        }
        mongoResponse = Promise.reject(fakeError)
        let fakeRes = sinon.mock()
        mdl.getOne(fakeReq, fakeRes, fakeCallback)
      })

      it('should call next callback with error if mongodb return empty result', (done) => {
        let fakeReq = {
          params: {
            id: faker.random.number()
          },
          user: {
            _id: faker.random.number()
          }
        }
        let expectedError = new Error('No todo item found.')

        let fakeCallback = (err) => {
          assert.equal(expectedError.message, err.message)
          done()
        }

        mongoResponse = Promise.resolve(null)
        let fakeRes = sinon.mock()
        mdl.getOne(fakeReq, fakeRes, fakeCallback)
      })

    })
  })
})

model.test.js

model.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')

describe('todo/model', () => {
  describe('todo schema', () => {
    let mongooseStub, SchemaConstructorSpy
    let ObjectIdFake, mongooseModelSpy, SchemaSpy

    before(() => {
      ObjectIdFake = faker.lorem.word()
      SchemaConstructorSpy = sinon.spy()
      SchemaSpy = sinon.spy()

      class SchemaStub {
        constructor(...args) {
          SchemaConstructorSpy(...args)
          return SchemaSpy
        }
      }

      SchemaStub.Types = {
        ObjectId: ObjectIdFake
      }

      mongooseModelSpy = sinon.spy()
      mongooseStub = {
        "Schema": SchemaStub,
        "model": mongooseModelSpy
      }

      proxyquire('../todo/model.js',
        {
          'mongoose': mongooseStub
        }
      )
    })

    it('should return new Todo model by schema', () => {
      let todoSchema = {
        title: {
          type: String
        },

        _user: {
          type: ObjectIdFake,
          ref: 'User'
        }
      }
      sinon.assert.calledOnce(SchemaConstructorSpy)
      sinon.assert.calledWith(SchemaConstructorSpy, todoSchema)

      sinon.assert.calledOnce(mongooseModelSpy)
      sinon.assert.calledWith(mongooseModelSpy, 'Todo', SchemaSpy)
    })
  })
})

routes.test.js

routes.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')

describe('todo/routes', () => {
  describe('router', () => {
    let expressStub, controllerStub, RouterStub, rootRouteStub, idRouterStub

    before(() => {
      rootRouteStub = {
        "get": sinon.stub().callsFake(() => rootRouteStub),
        "post": sinon.stub().callsFake(() => rootRouteStub)
      }
      idRouterStub = {
        "get": sinon.stub().callsFake(() => idRouterStub),
        "put": sinon.stub().callsFake(() => idRouterStub),
        "delete": sinon.stub().callsFake(() => idRouterStub)
      }
      RouterStub = {
        route: sinon.stub().callsFake((route) => {
          if (route === '/:id') {
            return idRouterStub
          }
          return rootRouteStub
        })
      }

      expressStub = {
        Router: sinon.stub().returns(RouterStub)
      }

      controllerStub = {
        getAll: sinon.mock(),
        create: sinon.mock(),
        getOne: sinon.mock(),
        update: sinon.mock(),
        delete: sinon.mock()
      }

      proxyquire('../todo/routes.js',
        {
          'express': expressStub,
          './controller': controllerStub
        }
      )
    })

    it('should map root get router with getAll controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/')
      sinon.assert.calledWith(rootRouteStub.get, controllerStub.getAll)
    })

    it('should map root post router with create controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/')
      sinon.assert.calledWith(rootRouteStub.post, controllerStub.create)
    })

    it('should map /:id get router with getOne controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/:id')
      sinon.assert.calledWith(idRouterStub.get, controllerStub.getOne)
    })

    it('should map /:id put router with update controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/:id')
      sinon.assert.calledWith(idRouterStub.put, controllerStub.update)
    })

    it('should map /:id delete router with delete controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/:id')
      sinon.assert.calledWith(idRouterStub.delete, controllerStub.delete)
    })
  })
})

serializer.test.js

serializer.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')

describe('todo/serializer', () => {
  describe('json serializer', () => {
    let JSONAPISerializerStub, SerializerConstructorSpy

    before(() => {
      SerializerConstructorSpy = sinon.spy()

      class SerializerStub {
        constructor(...args) {
          SerializerConstructorSpy(...args)
        }
      }

      JSONAPISerializerStub = {
        Serializer: SerializerStub
      }

      proxyquire('../todo/serializer.js',
        {
          'jsonapi-serializer': JSONAPISerializerStub
        }
      )
    })

    it('should return new instance of Serializer', () => {
      let schema = {
        attributes: ['title', '_user']
        ,
        _user: {
          ref: 'id',
          attributes: ['username']
        }
      }
      sinon.assert.calledOnce(SerializerConstructorSpy)
      sinon.assert.calledWith(SerializerConstructorSpy, 'todos', schema)
    })
  })
})

如何使用Sinon对单元测试节点API(Express with Mongo DB)

#1


2  

Hi I've created some test for you to understand how to use mocks.

嗨,我已经为您创建了一些测试,以了解如何使用模拟。

Full example github/nodejs_unit_tests_example

完整示例github / nodejs_unit_tests_example

controller.test.js

controller.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')
const assert = require('chai').assert

describe('todo/controller', () => {
  describe('controller', () => {

    let mdl
    let modelStub, serializerStub, populateMethodStub, fakeData
    let fakeSerializedData, fakeError
    let mongoResponse

    before(() => {
      fakeData = faker.helpers.createTransaction()
      fakeError = faker.lorem.word()
      populateMethodStub = {
        populate: sinon.stub().callsFake(() => mongoResponse)
      }
      modelStub = {
        find: sinon.stub().callsFake(() => {
          return populateMethodStub
        }),
        findOne: sinon.stub().callsFake(() => {
          return populateMethodStub
        })
      }

      fakeSerializedData = faker.helpers.createTransaction()
      serializerStub = {
        serialize: sinon.stub().callsFake(() => {
          return fakeSerializedData
        })
      }

      mdl = proxyquire('../todo/controller.js',
        {
          './model': modelStub,
          './serializer': serializerStub
        }
      )
    })

    beforeEach(() => {
      modelStub.find.resetHistory()
      modelStub.findOne.resetHistory()
      populateMethodStub.populate.resetHistory()
      serializerStub.serialize.resetHistory()
    })

    describe('getAll', () => {
      it('should return serialized search result from mongodb', (done) => {
        let resolveFn
        let fakeCallback = new Promise((res, rej) => {
          resolveFn = res
        })
        mongoResponse = Promise.resolve(fakeData)
        let fakeRes = {
          json: sinon.stub().callsFake(() => {
            resolveFn()
          })
        }
        mdl.getAll(null, fakeRes, null)

        fakeCallback.then(() => {
          sinon.assert.calledOnce(modelStub.find)
          sinon.assert.calledWith(modelStub.find, {})

          sinon.assert.calledOnce(populateMethodStub.populate)
          sinon.assert.calledWith(populateMethodStub.populate, '_user', '-password')

          sinon.assert.calledOnce(serializerStub.serialize)
          sinon.assert.calledWith(serializerStub.serialize, fakeData)

          sinon.assert.calledOnce(fakeRes.json)
          sinon.assert.calledWith(fakeRes.json, fakeSerializedData)
          done()
        }).catch(done)
      })

      it('should call next callback if mongo db return exception', (done) => {
        let fakeCallback = (err) => {
          assert.equal(fakeError, err)
          done()
        }
        mongoResponse = Promise.reject(fakeError)
        let fakeRes = sinon.mock()
        mdl.getAll(null, fakeRes, fakeCallback)
      })

    })

    describe('getOne', () => {

      it('should return serialized search result from mongodb', (done) => {
        let resolveFn
        let fakeCallback = new Promise((res, rej) => {
          resolveFn = res
        })
        mongoResponse = Promise.resolve(fakeData)
        let fakeRes = {
          json: sinon.stub().callsFake(() => {
            resolveFn()
          })
        }

        let fakeReq = {
          params: {
            id: faker.random.number()
          },
          user: {
            _id: faker.random.number()
          }
        }
        let findParams = {
          '_id': fakeReq.params.id,
          '_user': fakeReq.user._id
        }
        mdl.getOne(fakeReq, fakeRes, null)

        fakeCallback.then(() => {
          sinon.assert.calledOnce(modelStub.findOne)
          sinon.assert.calledWith(modelStub.findOne, findParams)

          sinon.assert.calledOnce(populateMethodStub.populate)
          sinon.assert.calledWith(populateMethodStub.populate, '_user', '-password')

          sinon.assert.calledOnce(serializerStub.serialize)
          sinon.assert.calledWith(serializerStub.serialize, fakeData)

          sinon.assert.calledOnce(fakeRes.json)
          sinon.assert.calledWith(fakeRes.json, fakeSerializedData)
          done()
        }).catch(done)
      })

      it('should call next callback if mongodb return exception', (done) => {
        let fakeReq = {
          params: {
            id: faker.random.number()
          },
          user: {
            _id: faker.random.number()
          }
        }
        let fakeCallback = (err) => {
          assert.equal(fakeError, err)
          done()
        }
        mongoResponse = Promise.reject(fakeError)
        let fakeRes = sinon.mock()
        mdl.getOne(fakeReq, fakeRes, fakeCallback)
      })

      it('should call next callback with error if mongodb return empty result', (done) => {
        let fakeReq = {
          params: {
            id: faker.random.number()
          },
          user: {
            _id: faker.random.number()
          }
        }
        let expectedError = new Error('No todo item found.')

        let fakeCallback = (err) => {
          assert.equal(expectedError.message, err.message)
          done()
        }

        mongoResponse = Promise.resolve(null)
        let fakeRes = sinon.mock()
        mdl.getOne(fakeReq, fakeRes, fakeCallback)
      })

    })
  })
})

model.test.js

model.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')

describe('todo/model', () => {
  describe('todo schema', () => {
    let mongooseStub, SchemaConstructorSpy
    let ObjectIdFake, mongooseModelSpy, SchemaSpy

    before(() => {
      ObjectIdFake = faker.lorem.word()
      SchemaConstructorSpy = sinon.spy()
      SchemaSpy = sinon.spy()

      class SchemaStub {
        constructor(...args) {
          SchemaConstructorSpy(...args)
          return SchemaSpy
        }
      }

      SchemaStub.Types = {
        ObjectId: ObjectIdFake
      }

      mongooseModelSpy = sinon.spy()
      mongooseStub = {
        "Schema": SchemaStub,
        "model": mongooseModelSpy
      }

      proxyquire('../todo/model.js',
        {
          'mongoose': mongooseStub
        }
      )
    })

    it('should return new Todo model by schema', () => {
      let todoSchema = {
        title: {
          type: String
        },

        _user: {
          type: ObjectIdFake,
          ref: 'User'
        }
      }
      sinon.assert.calledOnce(SchemaConstructorSpy)
      sinon.assert.calledWith(SchemaConstructorSpy, todoSchema)

      sinon.assert.calledOnce(mongooseModelSpy)
      sinon.assert.calledWith(mongooseModelSpy, 'Todo', SchemaSpy)
    })
  })
})

routes.test.js

routes.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')

describe('todo/routes', () => {
  describe('router', () => {
    let expressStub, controllerStub, RouterStub, rootRouteStub, idRouterStub

    before(() => {
      rootRouteStub = {
        "get": sinon.stub().callsFake(() => rootRouteStub),
        "post": sinon.stub().callsFake(() => rootRouteStub)
      }
      idRouterStub = {
        "get": sinon.stub().callsFake(() => idRouterStub),
        "put": sinon.stub().callsFake(() => idRouterStub),
        "delete": sinon.stub().callsFake(() => idRouterStub)
      }
      RouterStub = {
        route: sinon.stub().callsFake((route) => {
          if (route === '/:id') {
            return idRouterStub
          }
          return rootRouteStub
        })
      }

      expressStub = {
        Router: sinon.stub().returns(RouterStub)
      }

      controllerStub = {
        getAll: sinon.mock(),
        create: sinon.mock(),
        getOne: sinon.mock(),
        update: sinon.mock(),
        delete: sinon.mock()
      }

      proxyquire('../todo/routes.js',
        {
          'express': expressStub,
          './controller': controllerStub
        }
      )
    })

    it('should map root get router with getAll controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/')
      sinon.assert.calledWith(rootRouteStub.get, controllerStub.getAll)
    })

    it('should map root post router with create controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/')
      sinon.assert.calledWith(rootRouteStub.post, controllerStub.create)
    })

    it('should map /:id get router with getOne controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/:id')
      sinon.assert.calledWith(idRouterStub.get, controllerStub.getOne)
    })

    it('should map /:id put router with update controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/:id')
      sinon.assert.calledWith(idRouterStub.put, controllerStub.update)
    })

    it('should map /:id delete router with delete controller', () => {
      sinon.assert.calledWith(RouterStub.route, '/:id')
      sinon.assert.calledWith(idRouterStub.delete, controllerStub.delete)
    })
  })
})

serializer.test.js

serializer.test.js

const proxyquire = require('proxyquire')
const sinon = require('sinon')

describe('todo/serializer', () => {
  describe('json serializer', () => {
    let JSONAPISerializerStub, SerializerConstructorSpy

    before(() => {
      SerializerConstructorSpy = sinon.spy()

      class SerializerStub {
        constructor(...args) {
          SerializerConstructorSpy(...args)
        }
      }

      JSONAPISerializerStub = {
        Serializer: SerializerStub
      }

      proxyquire('../todo/serializer.js',
        {
          'jsonapi-serializer': JSONAPISerializerStub
        }
      )
    })

    it('should return new instance of Serializer', () => {
      let schema = {
        attributes: ['title', '_user']
        ,
        _user: {
          ref: 'id',
          attributes: ['username']
        }
      }
      sinon.assert.calledOnce(SerializerConstructorSpy)
      sinon.assert.calledWith(SerializerConstructorSpy, 'todos', schema)
    })
  })
})

如何使用Sinon对单元测试节点API(Express with Mongo DB)