Есть ли способ заставить Чая работать с асинхронными тестами Mocha?

Я запускаю некоторые асинхронные тесты в Mocha с помощью браузера Runner, и я пытаюсь использовать утверждения о ожиданиях Chai:

window.expect = chai.expect;
describe('my test', function() {
  it('should do something', function (done) {
    setTimeout(function () {
      expect(true).to.equal(false);
    }, 100);
  }
}

Это не дает мне нормальное сообщение об ошибке, но я получаю:

Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :)
    at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11)
    at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8)
    at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10)

Таким образом, очевидно, что он поймал ошибку, просто не отображая ее правильно. Есть идеи, как это сделать? Думаю, я мог бы просто называть "сделанным" с объектом ошибки, но потом я теряю всю элегантность чего-то вроде Чай, и он становится очень неуклюжим...

Ответ 1

Ваш асинхронный тест генерирует исключение при неудачных expect() айонах, которые не могут быть записаны с помощью it(), потому что исключение выбрано вне области it().

Захваченное исключение, которое вы видите, отображается с помощью process.on('uncaughtException') в разделе node или с помощью window.onerror() в браузере.

Чтобы устранить эту проблему, вам нужно зафиксировать исключение в асинхронной функции, называемой setTimeout(), чтобы вызвать done() с исключением в качестве первого параметра. Вам также нужно вызвать done() без параметра для указания успеха, иначе мокка сообщит об ошибке таймаута, потому что ваша тестовая функция никогда не сигнализировала бы, что это было сделано:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function ( done ) {
    // done() is provided by it() to indicate asynchronous completion
    // call done() with no parameter to indicate that it() is done() and successful
    // or with an error to indicate that it() failed
    setTimeout( function () {
      // Called from the event loop, not it()
      // So only the event loop could capture uncaught exceptions from here
      try {
        expect( true ).to.equal( false );
        done(); // success: call done with no parameter to indicate that it() is done()
      } catch( e ) {
        done( e ); // failure: call done with an error Object to indicate that it() failed
      }
    }, 100 );
    // returns immediately after setting timeout
    // so it() can no longer catch exception happening asynchronously
  }
}

Выполнение этого во всех ваших тестовых случаях раздражает, а не DRY, поэтому вы можете предоставить функцию, которая сделает это за вас. Позвольте называть эту функцию check():

function check( done, f ) {
  try {
    f();
    done();
  } catch( e ) {
    done( e );
  }
}

С помощью check() вы можете переписать свои асинхронные тесты следующим образом:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function( done ) {
    setTimeout( function () {
      check( done, function() {
        expect( true ).to.equal( false );
      } );
    }, 100 );
  }
}

Ответ 2

Вот мои тесты для ES6/ES2015 promises и ES7/ES2016 async/await. Надеюсь, это даст хороший обновленный ответ для тех, кто исследует эту тему:

import { expect } from 'chai'

describe('Mocha', () => {
  it('works synchronously', () => {
    expect(true).to.equal(true)
  })

  it('works ansyncronously', done => {
    setTimeout(() => {
      expect(true).to.equal(true)
      done()
    }, 4)
  })

  it('throws errors synchronously', () => {
    return true
    throw new Error('it works')
  })

  it('throws errors ansyncronously', done => {
    setTimeout(() => {
      return done()
      done(new Error('it works'))
    }, 4)
  })

  it('uses promises', () => {
    var testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    testPromise.then(result => {
      expect(result).to.equal('Hello')
    }, reason => {
      throw new Error(reason)
    })
  })

  it('uses es7 async/await', async (done) => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    try {
      const result = await testPromise
      expect(result).to.equal('Hello')
      done()
    } catch(err) {
      done(err)
    }
  })

  /*
  *  Higher-order function for use with async/await (last test)
  */
  const mochaAsync = fn => {
    return async (done) => {
      try {
        await fn()
        done()
      } catch (err) {
        done(err)
      }
    }
  }

  it('uses a higher order function wrap around async', mochaAsync(async () => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    expect(await testPromise).to.equal('Hello')
  }))
})

Ответ 3

Если вам нравится обещание, попробуйте Chai as Promised + Q, которые позволяют что-то вроде этого

doSomethingAsync().should.eventually.equal("foo").notify(done);

Ответ 4

Я спросил то же самое в списке рассылки Mocha. Они в основном сказали мне следующее: написать асинхронный тест с Моккой и Чаем:

  • всегда начинайте тест с if (err) done(err);
  • всегда заканчивайте тест с помощью done().

Он решил мою проблему и не менял ни одной строки моего кода между ними (ожидания Chai среди других). setTimout - это не способ проведения асинхронных тестов.

Здесь ссылка на обсуждение в списке рассылки.

Ответ 5

Я опубликовал пакет, который разрешает эту проблему.

Сначала установите пакет check-chai:

npm install --save check-chai

Затем в ваших тестах используйте chai.use(checkChai);, а затем используйте вспомогательную функцию chai.check, как показано ниже:

var chai = require('chai');
var dirtyChai = require('dirty-chai');
var checkChai = require('check-chai');
var expect = chai.expect;
chai.use(dirtyChai);
chai.use(checkChai);

describe('test', function() {

  it('should do something', function(done) {

    // imagine you have some API call here
    // and it returns (err, res, body)
    var err = null;
    var res = {};
    var body = {};

    chai.check(done, function() {
      expect(err).to.be.a('null');
      expect(res).to.be.an('object');
      expect(body).to.be.an('object');
    });

  });

});

Per Есть ли способ заставить Chai работать с асинхронными тестами Mocha? Я опубликовал это как пакет NPM.

Подробнее см. https://github.com/niftylettuce/check-chai.

Ответ 7

Очень связанный с ответом Jean Vincent и вдохновленный <, мы используем вспомогательную функцию, аналогичную его функции check, но мы называем ее eventually вместо этого ( это помогает ему соответствовать соглашениям об именах chai-as-обещано). Он возвращает функцию, которая принимает любое количество аргументов и передает их в исходный обратный вызов. Это помогает устранить дополнительный вложенный функциональный блок в ваших тестах и ​​позволяет обрабатывать любой тип обратного вызова async. Здесь написано в ES2015:

function eventually(done, fn) {
  return (...args) => {
    try {
      fn(...args);
      done();
    } catch (err) {
      done(err);
    }
  };
};

Пример использования:

describe("my async test", function() {
  it("should fail", function(done) {
    setTimeout(eventually(done, (param1, param2) => {
      assert.equal(param1, "foo");   // this should pass
      assert.equal(param2, "bogus"); // this should fail
    }), 100, "foo", "bar");
  });
});

Ответ 8

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

обратные вызовы событий

function expectEventCallback(done, fn) {
  return function() {
    try { fn(...arguments); }
    catch(error) { return done(error); }
    done();
  };
}

обратные вызовы стиля узла

function expectNodeCallback(done, fn) {
  return function(err, ...args) {
    if (err) { return done(err); }
    try { fn(...args); }
    catch(error) { return done(error); }
    done();
  };
}

пример использования

it('handles event callbacks', function(done) {
  something.on('event', expectEventCallback(done, (payload) => {
    expect(payload).to.have.propertry('foo');
  }));
});

it('handles node callbacks', function(done) {
  doSomething(expectNodeCallback(done, (payload) => {
    expect(payload).to.have.propertry('foo');
  }));
});

Ответ 9

Я решил, что извлечение try/catch в функцию.

function asyncExpect(test, done){
    try{
        test();
        done();
    } catch(error){
        done(error);
    }
}

Тогда в it() я вызываю:

it('shall update a host', function (done) {
            testee.insertHost({_id: 'host_id'})
                .then(response => {
                    asyncExpect(() => {
                        expect(response).to.have.property('ok', 1);
                        expect(response).to.have.property('nModified', 1);
                    }, done);
                });

        });

Это также отладка.

Ответ 10

Основываясь на этой ссылке, предоставленной @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/, описание может использовать возвращенное обещание, если вы опустите параметр done.

Только недостаток там должен быть Promise, а не любая асинхронная функция (вы можете обернуть ее обещанием, ты). Но в этом случае код может быть чрезвычайно уменьшен.

Он учитывает недостатки либо в начальной функции funcThatReturnsAPromise, либо в ожиданиях:

it('should test Promises', function () { // <= done removed
    return testee.funcThatReturnsAPromise({'name': 'value'}) // <= return added
        .then(response => expect(response).to.have.property('ok', 1));
});

Ответ 11

Таймеры во время тестов и асинхронности звучат довольно грубо. Есть способ сделать это с помощью подхода, основанного на обещаниях.

const sendFormResp = async (obj) => {
    const result = await web.chat.postMessage({
        text: 'Hello world!',
    });
   return result
}

Эта асинхронная функция использует веб-клиент (в данном случае это Slacks SDK). SDK заботится об асинхронной природе вызова API и возвращает полезную нагрузку. Затем мы можем проверить полезную нагрузку в chai, запустив expect объект против объекта, возвращенного в асинхронном обещании.

describe("Slack Logic For Working Demo Environment", function (done) {
    it("Should return an object", () => {
        return sdkLogic.sendFormResp(testModels.workingModel).then(res => {
            expect(res).to.be.a("Object");
        })
    })
});

Ответ 12

Что работало очень хорошо для меня icm Mocha/Chai был fakeTimer из Sinon Library. Просто продвигайте таймер в тесте, когда это необходимо.

var sinon = require('sinon');
clock = sinon.useFakeTimers();
// Do whatever. 
clock.tick( 30000 ); // Advances the JS clock 30 seconds.

Имеет ли дополнительный бонус, чтобы тест был завершен быстрее.

Ответ 13

Вы также можете использовать модуль домена. Например:

var domain = require('domain').create();

domain.run(function()
{
    // place you code here
});

domain.on('error',function(error){
    // do something with error or simply print it
});