【问题标题】:Using Sinon and Chai with ES6 constructor在 ES6 构造函数中使用 Sinon 和 Chai
【发布时间】:2017-06-29 02:15:16
【问题描述】:

我正在尝试为我的班级创建单元测试,如下所示:

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)的构造函数是否使用正确的参数被调用。但我找不到方法,所以我目前正在诉诸他们的一种方法,这不是我想要的。

诗乃有没有办法做到这一点?我需要重构代码以适应诗浓的要求吗?

  1. 我还想为配置项提供测试值,例如端口,看看他们是否被使用。再一次,我在诗浓找不到办法做到这一点。

我也尝试了 1 和 2 的 createStubInstance,但不断出错。

我们将不胜感激。

【问题讨论】:

  • 如果你不使用DI和ApiService等不作为属性导出,比如exports.ApiService = ApiService,你就无法正常模拟/窥探它们。使用rewireproxyquire 控制require 调用。
  • 好的,我会看看这些库,看看如何使用它们。希望获得有关如何将上述内容转换为使用 DI 等以允许正常模拟的提示
  • 对于 DI,检查任何 DI 库,例如injection-jsinversifypioc。我认为这不能用一个答案来解释,因为它需要一些重大的重构。对于现有的应用程序,我建议坚持使用rewire 等等。

标签: ecmascript-6 sinon


【解决方案1】:

为了使 CommonJS 模块无需额外措施即可测试,类应在整个应用程序中专门用作 exports 对象的属性。这些类应该就地从模块对象中解构。这不是很方便,但单独使用诗乃。

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)的面向测试的包。

【讨论】:

  • 谢谢@estus 我会尝试你的建议
【解决方案2】:

更新测试文件,感谢@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.....
    }))
  })

【讨论】:

    猜你喜欢
    • 2020-07-11
    • 1970-01-01
    • 2018-06-18
    • 2020-08-26
    • 2013-01-12
    • 1970-01-01
    • 1970-01-01
    • 2017-11-01
    • 1970-01-01
    相关资源
    最近更新 更多