¿Hay alguna forma de hacer que Chai funcione con pruebas asincrónicas de Mocha?

81

Estoy ejecutando algunas pruebas asincrónicas en Mocha usando el Browser Runner y estoy tratando de usar las afirmaciones de estilo de espera de Chai:

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

Esto no me da el mensaje normal de afirmación fallida, en su lugar obtengo:

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)

Entonces, obviamente, está detectando el error, simplemente no lo muestra correctamente. ¿Alguna idea de como hacer esto? Supongo que podría simplemente llamar "terminado" con un objeto de error, pero luego pierdo toda la elegancia de algo como Chai y se vuelve muy torpe ...

Thomas Parslow
fuente
El problema es el moka del lado del navegador. Consulte github.com/visionmedia/mocha/pull/278 para obtener información sobre esto.
Elliot Foster
A partir de 2020, debería echar un vistazo al chai-as-promisedcomplemento ...
Elmar Zander

Respuestas:

96

Su prueba asincrónica genera una excepción, en expect()aciones fallidas , que no se puede capturar it()porque la excepción se lanza fuera del it()alcance de.

La excepción capturada que ve que se muestra se captura usando process.on('uncaughtException')bajo nodo o usando window.onerror()en el navegador.

Para solucionar este problema, debe capturar la excepción dentro de la función asincrónica llamada por setTimeout()para llamar done()con la excepción como primer parámetro. También necesita llamar done()sin parámetro para indicar el éxito, de lo contrario, mocha informaría un error de tiempo de espera porque su función de prueba nunca habría señalado que se hizo:

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

Hacerlo en todos sus casos de prueba es molesto y no SECO, por lo que es posible que desee proporcionar una función para hacer esto por usted. Llamemos a esta función check():

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

Con check()ahora puede reescribir sus pruebas asincrónicas de la siguiente manera:

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 );
  }
}
Jean Vincent
fuente
Acabo de eliminar mi comentario anterior después de darme cuenta de que la parte de la que me quejaba (setTimeout) era en realidad de mi pregunta. ¡¡Lo siento!!
Thomas Parslow
2
La respuesta anterior parece incorrecta. Una expectativa fallida se lanzará inmediatamente y detendrá la prueba con un error significativo, no hay necesidad de un intento / captura complicado. Lo acabo de probar ahora mismo con una prueba de navegador.
Offirmo
3
Estaba luchando con este problema y encontré esta publicación de blog extremadamente útil: staxmanade.com/2015/11/…
RichardForrester
1
@RichardForrester, publicación extremadamente útil. ¡Gracias! Hacer que esta verificación funcione con Promises simplifica increíblemente el código. Pero tiene que ser con promesas (no con ninguna función asincrónica).
Pedro R.
1
Solo quiero avisar para la posteridad que este problema exacto ocurre con el nexttick () de Vue (que es un envoltorio para la promesa) y se puede manejar de la misma manera.
Eli Albert
20

Aquí están mis pruebas de aprobación para promesas de ES6 / ES2015 y async / await de ES7 / ES2016. Espero que esto proporcione una buena respuesta actualizada para cualquiera que esté investigando este tema:

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')
  }))
})
RichardForrester
fuente
@Pedro R. Cambié para eliminar hecho de la prueba de promesa. Como señaló, no es necesario.
RichardForrester
13

Si le gusta lo prometido, pruebe Chai como Promised + Q , que permite algo como esto:

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

Pregunté lo mismo en la lista de correo de Mocha. Básicamente me dijeron esto: escribir una prueba asincrónica con Mocha y Chai:

  • siempre comience la prueba con if (err) done(err);
  • siempre termine la prueba con done().

Resolvió mi problema y no cambió ni una sola línea de mi código en el medio (expectativas de Chai, entre otras). No setTimoutes la forma de realizar pruebas asíncronas.

Aquí está el enlace a la discusión en la lista de correo .

DjebbZ
fuente
1
La discusión a la que se vinculó es sobre chai y mocha del lado del servidor. El cartel pregunta sobre el moca y el chai del lado del navegador .
Elliot Foster
Ese no es el mismo problema. La setTimeoutfunción utilizada como ejemplo en esta pregunta no tiene ningún error en su devolución de llamada.
Sylvain B
1

Publiqué un paquete que resuelve este problema.

Primero instale el check-chaipaquete:

npm install --save check-chai

Luego, en sus pruebas, use chai.use(checkChai);y luego use la chai.checkfunción auxiliar como se muestra a continuación:

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

  });

});

Por ¿Hay una manera de conseguir Chai trabajar con pruebas Mocha asíncronos? Publiqué esto como un paquete de NPM.

Consulte https://github.com/niftylettuce/check-chai para obtener más información.

ingeniosa lechuga
fuente
1

Muy relacionado e inspirado por la respuesta de Jean Vincent , empleamos una función auxiliar similar a su checkfunción, pero la llamamos en su eventuallylugar (esto ayuda a que coincida con las convenciones de nomenclatura de chai-as-prometida). Devuelve una función que toma cualquier número de argumentos y los pasa a la devolución de llamada original. Esto ayuda a eliminar un bloque de funciones anidado adicional en sus pruebas y le permite manejar cualquier tipo de devolución de llamada asíncrona. Aquí está escrito en ES2015:

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

Ejemplo de uso:

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");
  });
});
Ryan McGeary
fuente
1

Sé que hay muchas respuestas repetidas y paquetes sugeridos para resolver esto, sin embargo, no he visto que las soluciones simples anteriores ofrezcan un patrón conciso para los dos casos de uso. Estoy publicando esto como una respuesta consolidada para otros que desean copiar pasta:

devoluciones de llamada de eventos

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

devoluciones de llamada de estilo de nodo

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

uso de ejemplo

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

Basado en este enlace proporcionado por @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/ , describe puede usar una Promesa devuelta si omite el hecho parámetro.

El único inconveniente es que tiene que haber una Promesa allí, no una función asincrónica (puedes envolverla con una Promesa, tú). Pero en este caso, el código puede reducirse en extremo.

Tiene en cuenta las fallas de la función inicial funcThatReturnsAPromise o de las expectativas:

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

Lo resolví extrayendo try/catcha una función.

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

Entonces en it()llamo:

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

        });

También es debugable.

Amio.io
fuente
0

Los temporizadores durante las pruebas y async suenan bastante difíciles. Hay una manera de hacer esto con un enfoque basado en promesas.

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

Esta función asincrónica utiliza un cliente web (en este caso, es Slacks SDK). El SDK se encarga de la naturaleza asincrónica de la llamada a la API y devuelve una carga útil. Luego, podemos probar la carga útil dentro de chai ejecutando expectel objeto devuelto en la promesa asincrónica.

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

Lo que funcionó muy bien para mí icm Mocha / Chai fue el fakeTimer de la biblioteca de Sinon. Simplemente avance el temporizador en la prueba cuando sea necesario.

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

Tiene la ventaja adicional de que la prueba se completa más rápido.

TinkerTank
fuente
1
Definitivamente me he encontrado usando principalmente soluciones como esta ahora al probar el código asincrónico. Es bueno haber "hecho" la devolución de llamada Mocha (como se muestra en la respuesta de Jean Vincent anterior) pero las pruebas suelen ser más fáciles de escribir cuando no las usa.
Thomas Parslow
-2

También puede utilizar el módulo de dominio. Por ejemplo:

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
});
abhishek singh bais
fuente