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 ...
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 );
}
}
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')
}))
})
Si vous aimez promis, essayez Chai comme promis + Q , qui permet quelque chose comme ceci:
doSomethingAsync().should.eventually.equal("foo").notify(done);
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:
if (err) done(err);
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 .
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.
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");
});
});
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:
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');
}));
});
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.
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));
});
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");
})
})
});