Как заглушить объектный метод с помощью sinon?

#node.js #sinon

#node.js #sinon

Вопрос:

Мне нужно заглушить метод sendMandrill объекта mh.

Смотрите мой тестируемый файл (mail.js ):

 let MailHandler = require('../../modules/mail.handler.module');
...
let api = (router, parser) => {
   let send = async (req, res, next) => {
      let mh = new MailHandler();
      mh.sendMandrill();    
      ...
   }
   ...    
   return router.post('/mail/send', parser.json(), send);
}
module.exports = api;
...
  

Мой тест (mail.spec.js ):

 let stRequest = require('supertest');
let MailHandler = require('../../modules/mail.handler.module');
describe('my test', () => {
   beforeEach(() => {
      sinon.stub(MailHandler.prototype, 'sendMandrill', () => true);
   })
   it('stubs sendMandrill!', done => {
      stRequest(app)
         .post('/mail/send')
            .end((err, resp) => {
                done();
            });
   })
})
  

В настоящее время я получаю сообщение об ошибке ниже:

 TypeError: Cannot stub non-existent own property sendMandrill
  

Добавление mail.handler.module — Смотрите ниже код MailHandler / sendMandrill:

 module.exports = mailHandler;

function mailHandler() {
    ...
    var mandrill = require('../modules/mandrill');

    var handler = {
        sendMandrill: sendMandrill,
        ...
    };

    return handler;

    function sendMandrill() {
        mandrill.messages.sendTemplate({
            message: {...}
        });
    }
    ...
}
  

Комментарии:

1. Можете ли вы опубликовать фрагмент модуля обработки почты?

2. @WakeskaterX почему это имеет значение? Подумайте о MailHandler как об универсальном классе, который должен быть создан, а метод, который должен быть заглушен, находится в результирующем объекте.

3. Попробуйте изменить: sinon.stub(MailHandler.prototype, 'sendMandrill', () => true); на: sinon.stub(MailHandler, 'sendMandrill', () => true); , чтобы узнать больше: medium.com/@alfasin/stubbing-with-sinon-4d6539caf365

4. Спасибо @alfasin — к сожалению, я получаю ту же ошибку.

5. Это означает, что у MailHandler такой функции нет. Вы уверены, что импортируете его правильно? Попробуйте нажать на него с помощью отладчика, чтобы увидеть, как MailHandler выглядит

Ответ №1:

Ваш текущий подход создает новый sendMandrill для каждого экземпляра, созданного mailHandler factory. На самом деле вы должны называть его без new let mh = mailHandler() или даже лучше переименовать его в createMailHandler , чтобы избежать неправильного использования.

Если вы хотите эффективно использовать наследование прототипов, вам нужно будет переписать, mailHandler чтобы использовать фактическое использование this вместо вновь созданного объекта.

 var mandrill = require('../modules/mandrill');

module.exports = MailHandler;

function MailHandler() {
    // use this instead of newly created object
    this.foo = 'bar'

    // avoid explicit return
    // return handler;
}

// set methods to prototype
MailHandler.prototype.sendMandrill = function sendMandrill() {
        // use this instead of handler here
        mandrill.messages.sendTemplate({
            message: {...}
        });
    }
  

Используя описанный выше подход, вы могли бы заглушить свойства прототипа с помощью sinon и оправдать вызов конструктора с new ключевым словом.

UPD

Если у вас нет контроля над mail.handler.module , вы могли бы либо использовать rewire модуль, который позволяет имитировать целые зависимости, либо выставлять MailHandler как часть вашего api модуля, чтобы сделать его инъекционным.

 api.MailHandler = require('../../modules/mail.handler.module')

let mh = api.MailHandler();
  

А затем в тестах

 let oldMailHandler;

beforeAll(() => { oldMailHandler = api.MailHandler})
afterAll(() => { api.MailHandler = oldMailHandler})
beforeEach(() => { api.MailHandler = function MockMailHandler() {} })
  

Комментарии:

1. Спасибо @Yuri Tarabanko. У меня нет контроля над mail.handler.module, поэтому я не могу там ничего изменить. Что мне нужно сделать, так это смоделировать зависимость, которую имеет функция, которую я должен протестировать («отправить»). «отправить» получает ссылку на объект, возвращаемый MailHandler() (новый экземпляр, если вызывается с помощью «new», или ссылку на существующий объект в противном случае, это не имеет значения). Я должен заглушить метод «sendMandrill» этого объекта. Вы хотите сказать, что единственный способ заглушить зависимости через sinon — это заглушить прототип?

2. @Miki Если вы не являетесь владельцем, mailHandler тогда я бы рекомендовал использовать rewire для макетирования всей зависимости. Или сделать его «вводимым», выполнив api.ModuleHandler = require('../../modules/mail.handler.module') и используя let mh = api.ModuleHandler() . тогда вы сможете внедрить любую реализацию в свои тесты.

3. Хорошо, я сделал MailHandler инъекционным, как вы предложили, и теперь я могу его отключить.