Чи є спосіб змусити Чая працювати з асинхронними тестами Мокки?


81

Я виконую деякі асинхронні тести в Mocha, використовуючи Browser 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)

Отже, очевидно, що помилка ловиться, просто неправильно відображається. Будь-які ідеї, як це зробити? Я думаю, я міг би просто назвати "готово" з об'єктом помилки, але тоді я втрачаю всю елегантність чогось на зразок Чай, і це стає дуже незграбним ...


Проблема в мокко з боку браузера. Для отримання інформації про це див. Github.com/visionmedia/mocha/pull/278 .
Елліот Фостер,

Станом на 2020 рік, ви повинні поглянути на chai-as-promisedплагін ...
Ельмар Зандер

Відповіді:


96

Ваш асинхронний тест генерує виняток із невдалих дій expect(), який неможливо захопити, it()оскільки виняток виведено за межі області it()дії.

Знятий виняток, який ви бачите відображеним, фіксується за допомогою process.on('uncaughtException')підвузла або за window.onerror()допомогою браузера.

Щоб виправити цю проблему, вам потрібно зафіксувати виняток в асинхронній функції, що викликається, setTimeout()для того, щоб викликати done()виняток як перший параметр. Вам також потрібно зателефонувати done()без параметра, щоб вказати успіх, інакше mocha повідомить про помилку тайм-ауту, оскільки ваша тестова функція ніколи не сигналізувала б про те, що це було зроблено:

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

Це робити на всіх ваших тестових випадках дратує, а не СУХО, тому ви можете надати функцію, яка робить це за вас. Давайте викличемо цю функцію 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 );
  }
}

Я щойно видалив свій попередній коментар після того, як зрозумів, що біт, на який я скаржився (setTimeout), насправді був із мого запитання. Вибачте !!
Томас Парслоу

2
Вищевказана відповідь видається неправильною. Невдале очікування негайно кине і зупинить тест зі значущою помилкою, немає необхідності в складній спробі / лові. Я щойно перевірив це за допомогою тесту браузера.
Offirmo

3
Я боровся з цим питанням і знайшов цю публікацію в блозі надзвичайно корисною: staxmanade.com/2015/11/…
RichardForrester

1
@RichardForrester, надзвичайно корисний пост. Дякую! Щоб це перевірити, робота з Promises неймовірно спрощує код. Але це повинно бути з обіцянками (не будь-якою функцією асинхронізації).
Педро Р.

1
Просто хочу сказати нащадкам, що саме ця проблема трапляється з Vue nexttick () (яка є обгорткою для обіцянки) і може бути оброблена таким же чином.
Елі Альберт,

20

Ось мої прохідні тести на обіцянки ES6 / ES2015 та 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')
  }))
})

@Pedro R. Я змінив, щоб видалити зроблене з обіцянки. Як ви зазначили, це не потрібно.
RichardForrester

13

Якщо вам подобається обіцяне, спробуйте Chai як Promised + Q , що дозволяє щось подібне:

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

2

Я запитав те саме у списку розсилки Мокки. Вони в основному сказали мені це: написати асинхронний тест з Моккою та Чаєм:

  • завжди починайте тест з if (err) done(err);
  • завжди закінчуйте тест з done().

Це вирішило мою проблему і не змінило жодного рядка мого коду між ними (серед інших очікувань Чай). Це setTimoutне спосіб робити асинхронні тести.

Ось посилання на обговорення у списку розсилки .


1
Обговорення, до якого ви зв’язали, стосується серверного chai та mocha. Плакат запитує про мокку та чай на стороні браузера .
Елліот Фостер

Це не одне питання. setTimeoutФункція використовується в якості прикладу в цьому питанні не має будь - які помилки в його функції зворотного виклику.
Sylvain B

1

Я опублікував пакет, який вирішує цю проблему.

Спочатку встановіть 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');
    });

  });

});

За Чи є спосіб , щоб отримати Chai працювати з асинхронними тестами Мокко? Я опублікував це як пакет NPM.

Для отримання додаткової інформації див. Https://github.com/niftylettuce/check-chai .



1

Дуже пов’язаний і натхненний відповіддю Жана Вінсента , ми використовуємо допоміжну функцію, подібну до його checkфункції, але ми називаємо її eventuallyзамість цього (це допомагає їй збігатися із правилами іменування chai-as-obeated). Він повертає функцію, яка приймає будь-яку кількість аргументів і передає їх у вихідний зворотний виклик. Це допомагає усунути зайвий вкладений функціональний блок у тестах і дозволяє обробляти будь-який тип асинхронного зворотного виклику. Ось це написано в 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");
  });
});

1

Я знаю, що існує багато повторних відповідей та запропонованих пакетів для вирішення цього, проте я не бачив простих рішень, наведених вище, що пропонують стислий шаблон для двох випадків використання. Я публікую це як зведену відповідь для інших, хто бажає скопіювати пасту:

зворотні дзвінки події

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

0

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

Єдиним недоліком має бути Promise, а не якась асинхронна функція (ти можеш обернути її 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));
});

0

Я вирішив його витяг 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);
                });

        });

Це також налагоджено.


0

Таймери під час тестів та асинхронізації звучать досить грубо. Є спосіб зробити це за допомогою підходу, заснованого на обіцянках.

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

-2

Для мене дуже добре працював icm Mocha / Chai - це підроблений Таймер із бібліотеки Сінона. Просто просуньте таймер у тесті, де це необхідно.

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

Має додатковий бонус за те, що тест проходить швидше.


1
Я точно визначив, що зараз використовую подібні рішення під час тестування асинхронного коду. Добре мати "зроблений" зворотний дзвінок Мокку (як показано у відповіді Жана Вінсента вище), але тести зазвичай легше писати, коли ти ними не користуєшся.
Томас Парслоу,

-2

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

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
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.