Czy istnieje sposób, aby Chai działał z asynchronicznymi testami Mocha?


81

Przeprowadzam kilka testów asynchronicznych w Mocha przy użyciu Browser Runner i próbuję użyć asercji oczekiwanego stylu Chai:

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

To nie daje mi normalnego komunikatu o niepowodzeniu potwierdzenia, zamiast tego otrzymuję:

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)

Więc oczywiście wychwytuje błąd, po prostu nie wyświetla go poprawnie. Jakieś pomysły, jak to zrobić? Myślę, że mógłbym po prostu nazwać „gotowe” z obiektem błędu, ale potem tracę całą elegancję czegoś takiego jak Chai i staje się bardzo niezgrabne ...


Problem dotyczy mokki po stronie przeglądarki. Więcej informacji na ten temat można znaleźć na github.com/visionmedia/mocha/pull/278 .
Elliot Foster

Od 2020 roku powinieneś rzucić okiem na chai-as-promisedwtyczkę ...
Elmar Zander

Odpowiedzi:


96

Twój test asynchroniczny generuje wyjątek w przypadku niepowodzenia expect(), którego nie można przechwycić, it()ponieważ wyjątek jest zgłaszany poza it()zakresem.

Przechwycony wyjątek, który widzisz jako wyświetlany, jest przechwytywany za pomocą process.on('uncaughtException')węzła lub za pomocąwindow.onerror() w przeglądarce.

Aby rozwiązać ten problem, należy przechwycić wyjątek w ramach funkcji asynchronicznej wywoływanej przez setTimeout(), aby wywołać done()wyjątek jako pierwszy parametr. Musisz również wywołać done()bez parametru, aby wskazać sukces, w przeciwnym razie mokka zgłosi błąd przekroczenia limitu czasu, ponieważ Twoja funkcja testowa nigdy nie zasygnalizowałaby, że została wykonana:

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

Robienie tego we wszystkich przypadkach testowych jest denerwujące i nie jest SUCHE, więc możesz chcieć udostępnić funkcję, która zrobi to za Ciebie. Nazwijmy tę funkcję check():

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

Dzięki check()możesz teraz przepisać testy asynchroniczne w następujący sposób:

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

Właśnie usunąłem swój poprzedni komentarz po tym, jak zdałem sobie sprawę, że fragment, na który narzekałem (setTimeout), był w rzeczywistości z mojego pytania. Przepraszam!!
Thomas Parslow,

2
Powyższa odpowiedź wydaje się błędna. Nieudane oczekiwanie spowoduje natychmiastowe odrzucenie i zatrzymanie testu ze znaczącym błędem, nie ma potrzeby wykonywania skomplikowanej próby / złapania. Właśnie przetestowałem to teraz za pomocą testu przeglądarki.
Offirmo


1
@RichardForrester, niezwykle pomocny post. Dzięki! Aby to sprawdzić, praca z Promises niewiarygodnie upraszcza kod. Ale musi być z obietnicami (nie z żadną funkcją asynchroniczną).
Pedro R.

1
Chcę tylko przypomnieć potomnym, że ten właśnie problem występuje w przypadku Vue nexttick () (który jest opakowaniem obietnicy) i można go rozwiązać w ten sam sposób.
Eli Albert

20

Oto moje zaliczone testy obietnic ES6 / ES2015 i async / await ES7 / ES2016. Mam nadzieję, że zapewnia to miłą zaktualizowaną odpowiedź dla każdego, kto bada ten temat:

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. Zmieniłem się, aby usunąć gotowe z testu obietnicy. Jak zauważyłeś, nie jest to potrzebne.
RichardForrester,

13

Jeśli podoba Ci się obietnica, wypróbuj Chai jako obiecaną + Q , które pozwalają na coś takiego:

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

2

O to samo zapytałem na liście mailingowej Mocha. Zasadniczo powiedzieli mi: napisać test asynchroniczny z Mocha i Chai:

  • zawsze rozpoczynaj test od if (err) done(err);
  • zawsze kończ test znakiem done().

To rozwiązało mój problem i nie zmieniło ani jednej linii kodu pomiędzy (między innymi oczekiwania Chai). PliksetTimout jest to sposób na wykonywanie testów asynchronicznych.

Oto link do dyskusji na liście mailingowej .


1
Dyskusja, z którą się łączysz, dotyczy chai i mokki po stronie serwera. Plakat pyta o mocha i herbatę po stronie przeglądarki .
Elliot Foster

To nie ten sam problem. setTimeoutFunkcja używany jako przykład w tej kwestii nie ma żadnego błędu w jego zwrotnego.
Sylvain B

1

Opublikowałem pakiet, który rozwiązuje ten problem.

Najpierw zainstaluj check-chaipakiet:

npm install --save check-chai

Następnie w swoich testach użyj, chai.use(checkChai);a następnie użyj chai.checkfunkcji pomocniczej, jak pokazano poniżej:

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 Czy istnieje sposób, aby Chai działał z asynchronicznymi testami Mocha?Opublikowałem to jako pakiet NPM.

Więcej informacji można znaleźć pod adresem https://github.com/niftylettuce/check-chai .



1

Bardzo mocno powiązana i zainspirowana odpowiedzią Jeana Vincenta , stosujemy funkcję pomocniczą podobną do jego checkfunkcji, ale nazywamy ją eventuallyzamiast tego (pomaga to dopasować się do konwencji nazewnictwa czaj-zgodnie z obietnicą). Zwraca funkcję, która przyjmuje dowolną liczbę argumentów i przekazuje je do pierwotnego wywołania zwrotnego. Pomaga to wyeliminować dodatkowy zagnieżdżony blok funkcyjny w testach i umożliwia obsługę dowolnego typu wywołania zwrotnego asynchronicznego. Tutaj jest napisane w ES2015:

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

Przykładowe zastosowanie:

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

Wiem, że istnieje wiele powtarzających się odpowiedzi i sugerowanych pakietów rozwiązania tego problemu, ale nie widziałem, aby proste rozwiązania powyżej oferowały zwięzły wzór dla dwóch przypadków użycia. Przesyłam to jako skonsolidowaną odpowiedź dla innych, którzy chcą skopiować makaron:

wywołania zwrotne zdarzeń

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

wywołania zwrotne w stylu węzła

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

przykład użycia

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

Na podstawie tego linku dostarczonego przez @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/ , opisz, że możesz użyć zwróconej Obietnicy, jeśli pominiesz gotowe parametr.

Jedynym minusem musi być obietnica, a nie funkcja asynchroniczna (możesz ją owinąć obietnicą). Ale w tym przypadku kod można bardzo zmniejszyć.

Uwzględnia wady zarówno w funkcji początkowej funkcji, która zwraca promise, jak i oczekiwania:

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

0

Rozwiązałem to wyodrębnianie try/catchdo funkcji.

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

Wtedy it()wzywam:

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

        });

Można go również debugować.


0

Liczniki czasu podczas testów i asynchronizacji brzmią dość szorstko. Jest na to sposób, stosując podejście oparte na obietnicach.

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

Ta funkcja asynchroniczna korzysta z klienta sieci Web (w tym przypadku jest to Slacks SDK). Zestaw SDK dba o asynchroniczny charakter wywołania interfejsu API i zwraca ładunek. Następnie możemy przetestować ładunek w chai, uruchamiając expectobiekt zwrócony w obietnicy asynchronicznej.

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

To, co działało bardzo dobrze dla mnie icm Mocha / Chai, to fałszywy Timer z Sinon's Library. W razie potrzeby wystarczy przesunąć licznik czasu w teście.

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

Ma dodatkową zaletę w postaci szybszego zakończenia testu.


1
Zdecydowanie znalazłem się głównie przy użyciu takich rozwiązań podczas testowania kodu asynchronicznego. Dobrze jest mieć „gotowe” wywołanie zwrotne Mocha (jak pokazano w odpowiedzi Jean Vincent powyżej), ale testy są zwykle łatwiejsze do napisania, gdy ich nie używasz.
Thomas Parslow,

-2

Możesz również użyć modułu domeny. Na przykład:

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
});
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.