使用Sinon和Chai与ES6构造函数

时间:2017-02-10 16:36:53

标签: ecmascript-6 sinon

我正在尝试为我的班级创建单元测试:

MyService.js:

const ApiServce = require('./api-service')
const Config = require('./config')
const Redis = require('ioredis')

class MyService {

  constructor () {
    const self = this

    self.apiService = new ApiServce('MyService', '1.0.0', Config.port)

    self.registerRoutes() //this invokes self.apiSerivce.registerRoutes

    self.redis = new Redis(Config.redisport, Config.redishost)
    self.queueKey = Config.redisqueuekey
  }
  run () {
    const self = this
    self.apiService.run()
  }
}

module.exports = MyService

Config.js

module.exports = {
  port: process.env.SVC_PORT || 8070,
  redishost: process.env.REDIS_HOST || '127.0.0.1',
  redisport: process.env.REDIS_PORT || 6379,
  redisqueuekey: process.env.REDIS_Q_KEY || 'myeventqueue'
}

测试文件:

const Redis = require('ioredis')
const MyService = require('../src/myservice')
const ApiService = require('../src/api-service')
const Chai = require('chai')
const Sinon = require('sinon')
const SinonChai = require('sinon-chai')

Chai.use(SinonChai)
const should = Chai.should()
const expect = Chai.expect

describe('MyService', function () {
  let apiservicestub, redisstub, apiconststub
  beforeEach(function () {
    apiservicestub = Sinon.stub(ApiService.prototype, 'registerRoutes')
    redisstub = Sinon.stub(Redis.prototype, 'connect')
    redisstub.returns(Promise.resolve())
  })



   describe('.constructor', function () {
     it('creates instances of api service and redis client with correct parameters', Sinon.test(function () {
       try {
        const service = new MyService()
        expect(apiservicestub).called
        expect(redisstub).called
       } catch (e) {
        console.error(e)
        expect(false)
      }
    }))

问题,问题:

  1. 我实际上想要(ed)测试使用正确的参数调用依赖类(apiservice和redis)的构造函数。但是我找不到方法,所以我现在正在使用他们不想要的方法之一。
  2. Sinon有没有办法实现这个目标?我是否需要重新构建代码以满足Sinon的要求?

    1. 我还想为Config项提供测试值,例如端口,看看他们是否被使用。再一次,我在Sinon找不到这样做的方法。
    2. 我也为1和2尝试了createStubInstance,但仍然遇到错误。

      任何建议都将受到赞赏。

2 个答案:

答案 0 :(得分:1)

为了使CommonJS模块可以在没有额外措施的情况下进行测试,类应该在整个应用程序中专门用作exports对象的属性。这些类应该从模块对象就地进行解析。这不太方便,但它只适用于Sinon。

class ApiService {...}
exports.ApiService  = ApiService;

...

const apiServiceModule = require('./api-service');

class MyService {
  constructor () {
    const { ApiService } = apiServiceModule;
    ...

在这种情况下,可以在MyService实例化之前模拟模块对象的属性。 Sinon间谍不能正确支持类,构造函数应该被包装:

sinon.stub(apiServiceModule, 'ApiService', function MockedApiService(...) {
  return new class { constructor (...) ... };
})

或者,可以使用DI,并且应该根据该重构应用程序。现有的DI库(injection-jsinversifypioc)可以合理地处理这个工作,但是一个简单的DI模式看起来像这样:

class MyService {
  constructor (ApiService, ...) {
    ...

在这种情况下,可以在构造中提供所有依赖项 - 包括应用程序和测试。

但最简单的方法是使用面向测试的软件包,这些软件包会混淆模块缓存并允许控制require个调用(rewireproxyquiremock-require

答案 1 :(得分:0)

更新了测试文件,感谢@estus的指示:

const Redis = require('ioredis')
const ApiService = require('../src/api-service')
const Chai = require('chai')
const Sinon = require('sinon')
const SinonChai = require('sinon-chai')
const Proxyquire = require('proxyquire')
const MyService = require('../src/myservice')

Chai.use(SinonChai)
const should = Chai.should()
const expect = Chai.expect

var namespace = {
  apiServiceStubClass: function () {
  },
  redisStubClass: function () {
  }
}

describe('MyService', function () {
  let ProxiedMyService
  let apiservicestub, redisstub, regroutestub, configstub, apiserviceregroutes, ioredisstub
  beforeEach(function () {
    apiservicestub = Sinon.stub(namespace, 'apiServiceStubClass')
    redisstub = Sinon.stub(namespace, 'redisStubClass')

    configstub = {
      version: 'testversion',
      port: 9999,
      redishost: 'testhost',
      redisport: 9999,
      redisrteventqueuekey: 'testqueyekey'
    }

    ProxiedMyService = Proxyquire('../src/myservice', {
      './api-service': apiservicestub,
      './config': configstub,
      'ioredis': redisstub
    })

    regroutestub = Sinon.stub(ProxiedMyService.prototype, 'registerRoutes')
    regroutestub.returns(true)

    apiserviceregroutes = Sinon.stub(ApiService.prototype, 'registerRoutes')
    regroutestub.returns(true)

    ioredisstub = Sinon.stub(Redis.prototype, 'connect')
    ioredisstub.returns(Promise.resolve())
  })

  afterEach(function () {
    namespace.apiServiceStubClass.restore()
    namespace.redisStubClass.restore()
    ProxiedMyService.prototype.registerRoutes.restore()
    ApiService.prototype.registerRoutes.restore()
    Redis.prototype.connect.restore()
  })

  describe('.constructor', function () {
    it('creates instances of api service and redis client with correct parameters', Sinon.test(function () {
      const service = new ProxiedMyService()
      expect(apiservicestub).to.have.been.calledWithNew
      expect(apiservicestub).to.have.been.calledWith('MyService', 'testversion', 9999)
      expect(regroutestub).to.have.been.called
      expect(redisstub).to.have.been.calledWithNew
      expect(redisstub).to.have.been.calledWith(9999, 'testhost')
      expect(service.queueKey).to.be.equal('testqueyekey')
    }))

    it('creates redis client using host only when port is -1', Sinon.test(function () {
      configstub.redisport = -1
      const service = new ProxiedMyService()
      expect(redisstub).to.have.been.calledWith('testhost')
    }))
  })

  describe('.registerRoutes', function () {
    it('calls apiService registerRoutes with correct url and handler', Sinon.test(function () {
      const service = new MyService()
      expect.....
    }))
  })