2017-02-10 2 views
0

Я пытаюсь создать модульные тесты для моего класса, который следующим образом:Использование Sinon и Chai с ES6 конструктору

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' 
} 

Test файл:

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) 
     } 
    })) 

Вопросы, Iss ues:

  1. На самом деле я хочу (ed) проверить, что конструкторы зависимых классов (apiservice и redis) вызываются с правильными параметрами. Но я не мог найти способ, поэтому в настоящее время я прибегаю к одному из своих методов, чего я не хочу.

Есть ли способ в Синоне достичь этого? Нужно ли мне реструктурировать код в соответствии с требованиями Синона?

  1. Я также хочу предоставить тестовые значения для элементов конфигурации, например. порт, чтобы узнать, будут ли они использоваться. Опять я не мог найти способ в Синоне сделать это.

Я попробовал createStubInstance как для 1, так и для 2, но продолжаю получать ошибки.

Любые советы будут оценены.

+1

Если вы не используете DI и ApiService, и т.д., не экспортируются как свойства, как exports.ApiService = ApiService, нет никакого способа, как вы можете имитировать/шпионить их нормально. Используйте 'rewire' или' proxyquire' для управления вызовами 'require'. – estus

+0

Хорошо, я посмотрю на эти библиотеки, чтобы узнать, как их использовать. Почитал бы советы о том, как преобразовать выше, чтобы использовать DI и т. Д., Чтобы обеспечить нормальное издевательство –

+1

Для DI, проверьте любую библиотеку DI, например. 'injection-js',' inversify', 'pioc'. Я не думаю, что это можно объяснить одним ответом, поскольку для этого требуется какой-то большой рефакторинг. Для существующего приложения я бы предложил придерживаться 'rewire' и так далее. – estus

ответ

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..... 
    })) 
    }) 
1

Для того, чтобы модули CommonJS тестировались без дополнительных мер, классы должны использоваться исключительно как свойства объекта exports через приложение. Классы должны быть деструктурированы из объекта модуля на месте. Это не очень удобно, но он работает только с Синоном.

I.e.

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-js, inversify, pioc) может справиться с этой работой разумно, но простой шаблон DI выглядит следующим образом:

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

В этом случае все зависимости могут быть поставлены на строительство - как в применении и в тестах.

Но самый простой способ заключается в использовании тест-ориентированных пакетов, связывайтесь с кэшем модуля и позволяют взять под контроль require вызовов (rewire, proxyquire, mock-require).

не
+0

Спасибо @estus Я попробую ваши предложения –

Смежные вопросы