web-dev-qa-db-fra.com

Existe-t-il un moyen de faire fonctionner Chai avec des tests asynchrones Mocha?

J'exécute des tests asynchrones dans Mocha à l'aide de Browser Runner et j'essaie d'utiliser les assertions de style attendues de Chai:

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

Cela ne me donne pas le message d'assertion normalement échoué, mais je reçois:

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)

Donc, évidemment, l'erreur est captée, mais elle ne l'affiche pas correctement. Des idees pour faire cela? Je suppose que je pourrais simplement appeler "done" avec un objet d'erreur, mais je perds alors toute l'élégance de quelque chose comme Chai et cela devient très maladroit ...

78
Thomas Parslow

Votre test asynchrone génère une exception, en cas d'échec de expect()ations, qui ne peut pas être capturée par it() car l'exception est levée en dehors de la portée de it().

L'exception capturée que vous voyez affichée est capturée à l'aide de process.on('uncaughtException') sous nœud ou à l'aide de window.onerror() dans le navigateur. 

Pour résoudre ce problème, vous devez capturer l'exception dans la fonction asynchrone appelée par setTimeout() afin d'appeler done() avec l'exception comme premier paramètre. Vous devez également appeler done() sans paramètre pour indiquer votre succès, sinon mocha signalerait une erreur de dépassement de délai car votre fonction de test n'aurait jamais signalé que cela avait été fait:

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

Faire cela sur tous vos tests est ennuyeux et non pas DRY, vous pouvez donc vouloir fournir une fonction pour le faire à votre place. Appelons cette fonction check():

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

Avec check(), vous pouvez maintenant réécrire vos tests asynchrones comme suit:

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 );
  }
}
92
Jean Vincent

Voici mes tests de réussite pour les promesses ES6/ES2015 et ES7/ES2016 async/wait. J'espère que ceci fournit une réponse mise à jour de Nice pour ceux qui recherchent ce sujet:

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')
  }))
})
18
RichardForrester

Si vous aimez promis, essayez Chai comme promis + Q , qui permet quelque chose comme ceci:

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

J'ai demandé la même chose dans la liste de diffusion Mocha. Ils m'ont essentiellement dit ceci: écrire un test asynchrone avec Mocha et Chai:

  • commencez toujours le test avec if (err) done(err);
  • toujours terminer le test avec done()

Cela a résolu mon problème et n'a pas changé une seule ligne de code entre les deux (attentes de Chai entre autres). La setTimout n'est pas la façon de faire des tests asynchrones.

Voici le lien vers la discussion dans la liste de diffusion .

2
DjebbZ

J'ai publié un package qui résout ce problème.

Commencez par installer le package check-chai:

npm install --save check-chai

Ensuite, dans vos tests, utilisez chai.use(checkChai);, puis utilisez la fonction d'assistance chai.check comme indiqué ci-dessous:

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 Existe-t-il un moyen de faire fonctionner Chai avec des tests asynchrones Mocha? J'ai publié ceci sous forme de package NPM.

Veuillez consulter https://github.com/niftylettuce/check-chai pour plus d'informations.

1
niftylettuce

Très lié et inspiré par la réponse de Jean Vincent , nous employons une fonction d’aide similaire à sa fonction check, mais nous l’appelons plutôt eventually (cela l’aide à s’aligner sur les conventions de nommage de chai-as-promise). Il retourne une fonction qui prend un nombre quelconque d'arguments et les transmet au rappel d'origine. Cela aide à éliminer un bloc de fonction imbriqué supplémentaire dans vos tests et vous permet de gérer tout type de rappel asynchrone. Ici, il est écrit dans ES2015:

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

Exemple d'utilisation:

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
Ryan McGeary

Je sais qu'il existe de nombreuses réponses répétées et des suggestions de solutions pour résoudre ce problème, mais je n'ai pas vu les solutions simples ci-dessus proposer un schéma concis pour les deux cas d'utilisation. Je publie cela comme une réponse consolidée pour les autres qui souhaitent copier des pâtes:

rappels d'événements

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

rappels de style de noeud

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

exemple d'utilisation

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

Je l'ai résolu en extrayant try/catch en une fonction.

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

Puis dans it() j'appelle:

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

        });

C'est aussi débogable.

0
Amio.io

Sur la base de ce lien fourni par @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/ , peut utiliser une promesse retournée si vous omettez le paramètre done. 

Seul inconvénient, il doit y avoir une promesse, pas une fonction asynchrone (vous pouvez l'envelopper avec une promesse, tu). Mais dans ce cas, le code peut être extrêmement réduit.

Il prend en compte les défaillances de la fonction funcThatReturnsAPromise initiale ou des attentes:

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

Les minuteries pendant les tests et l’async ont un son assez brut Il existe un moyen de le faire avec une approche basée sur les promesses.

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

Cette fonction asynchrone utilise un client Web (dans ce cas, Slacks SDK). Le SDK prend en charge la nature asynchrone de l'appel API et renvoie une charge utile. Nous pouvons ensuite tester la charge utile dans chai en exécutant expect par rapport à l'objet renvoyé dans la promesse asynchrone.

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