Как мне правильно протестировать обработку событий ошибок асинхронного потока с помощью mocha / sinon / chai?

#javascript #node.js #asynchronous #mocha.js

#javascript #node.js #асинхронный #mocha.js

Вопрос:

Я тестирую асинхронный метод, который возвращает некоторые данные из веб-запроса, используя собственный метод https.request() в NodeJS. Я использую mocha, chai и sinon с соответствующими расширениями для каждого.

Метод, который я тестирую, по сути, оборачивает шаблонный код https.request(), предоставленный в документах NodeJS, в обещание и разрешает при получении события ответа ‘end’ или отклоняет, если получено событие запроса ‘error’. Биты, относящиеся к обсуждению, выглядят следующим образом:

 async fetch(...) {
    // setup

    return new Promise((resolve, reject) => {
        const req = https.request(url, opts, (res) => {
            // handle response events
        });
        
        req.on('error', (e) => {
            logger.error(e);  // <-- this is what i want to verify
            reject(e);
        });

        req.end();
    });
}
 

Как указано в комментарии, я хочу проверить, что если выдается событие ошибки запроса, ошибка регистрируется правильно. Это то, что я сейчас делаю для достижения этой цели:

 it('should log request error events', async () => {
    const sut = new MyService();
    const err = new Error('boom');
    const req = new EventEmitter();
    req.end = sinon.fake();
    const res = new EventEmitter();
    sinon.stub(logger, 'error');
    sinon.stub(https, 'request').callsFake((url, opt, cb) => {
        cb(res);
        return req;
    });

    try {
        const response = sut.fetch(...);
        req.emit('error', err);
        await response;
    } catch() {}

    logger.error.should.have.been.calledOnceWith(err);
});
 

Это похоже на взлом, но я не могу понять, как это сделать правильно, используя обычные шаблоны. Основная проблема в том, что мне нужно выдать событие ошибки после вызова метода, но до того, как обещание будет выполнено, и я не вижу, как это сделать, если я возвращаю обещание, как вы обычно делаете с mocha.

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

1. Я не вижу в этом ничего плохого. В качестве альтернативы вы можете поместить a setTimeout(() => req.emit('error', err), demoDelay); внутри request подделки, а затем записать await sut.fetch(…); , но в любом случае все в порядке.

2. Что вы имеете в виду, когда говорите: » Я не вижу, как это сделать, если я возвращаю обещание, как вы обычно делаете с mocha». ? Ваша async функция возвращает обещание mocha.

3. @Bergi Ваша идея setTimeout дала мне ответ, который я искал, спасибо!

Ответ №1:

Я должен был подумать об этом, но комментарий @Bergi об использовании setTimeout() в подделке дал мне идею, и теперь я работаю с предпочтительным синтаксисом:

 it('should log request error events', () => {
    const sut = new MyService();
    const err = new Error('boom');
    const req = new EventEmitter();
    req.end = sinon.fake();
    const res = new EventEmitter();
    sinon.stub(logger, 'error');
    sinon.stub(https, 'request').callsFake((url, opt, cb) => {
        cb(res);
        setTimeout(() => { req.emit('error', err); });
        return req;
    });

    return sut.fetch(...).should.eventually.be.rejectedWith(err)
        .then(() => {
            logger.error.should.have.been.calledOnceWith(err);
        });
});
 

Мне не нравится добавлять какие-либо задержки в модульные тесты, если я специально не тестирую отложенную функциональность, поэтому я использовал setTimeout() с задержкой 0, чтобы просто отправить вызов emit в конец очереди сообщений. Переместив его в поддельный, я смог просто использовать цепочку обещаний для проверки вызова метода logger.

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

1. Ваш исходный код также использует цепочку обещаний, единственное отличие состоит в том, что он сохраняет обещание во временной переменной response . Я бы подумал, что async await было бы предпочтительнее использовать синтаксис / для цепочки вместо .then() вызовов?

2. @Bergi Я все еще учусь, но, думаю, я понимаю вашу точку зрения в том, что на самом деле происходит за кулисами. В исходном коде, несмотря на то, что проверка регистратора является отдельным оператором, поскольку это асинхронная функция, операторы после await объединяются в цепочку так же, как если бы вы вызывали then() . Это довольно проницательно, спасибо! И да, в обычном коде я предпочитаю async / await, но в случае модульных тестов я пытаюсь поддерживать согласованный стиль BDD, поэтому эта версия больше похожа на другие тесты, даже если есть небольшая разница.