Chaiを非同期Mochaテストで動作させる方法はありますか?


81

Browser Runnerを使用してMochaでいくつかの非同期テストを実行しており、Chaiのexpectスタイルアサーションを使用しようとしています。

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プラグインを確認する必要があります...
ElmarZander20年

回答:


96

非同期テストは、失敗したexpect()ationで例外を生成しますが、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
  }
}

すべてのテストケースでこれを行うのは煩わしく、DRYではないため、これを行う機能を提供することをお勧めします。この関数を呼び出しましょう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)が実際に私の質問からのものであることに気付いた後、以前のコメントを削除しました。ごめんなさい!!
Thomas Parslow 2013年

2
上記の答えは間違っているようです。期待に失敗すると、すぐにスローされ、意味のあるエラーでテストが停止します。複雑な試行/キャッチは必要ありません。私は今それをブラウザテストでテストしました。
Offirmo 2015

3
私はこの問題に苦しんで非常に役立つ、このブログの記事を見つけました:staxmanade.com/2015/11/...
RichardForrester

1
@RichardForrester、非常に役立つ投稿。ありがとう!このチェックをPromisesで機能させると、コードが非常に単純化されます。ただし、promiseを使用する必要があります(非同期関数ではありません)。
ペドロR.

1
この正確な問題がVuenexttick()(promiseのラッパー)で発生し、同じ方法で処理できることを後世に伝えたいだけです。
Eli Albert

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.promiseテストからdoneを削除するように変更しました。ご指摘のとおり、必要ありません。
RichardForrester 2016

13

約束が好きな場合は、ChaiをPromised + Qとして試してください。これにより、次のようなことが可能になります。

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

2

モカのメーリングリストでも同じことを聞いた。彼らは基本的に私にこれを言いました:MochaとChaiで非同期テストを書くために:

  • 常にテストを開始します if (err) done(err);
  • 常にdone()。でテストを終了します。

それは私の問題を解決し、その間に私のコードの1行を変更しませんでした(とりわけチャイの期待)。これsetTimoutは非同期テストを行う方法ではありません。

メーリングリストのディスカッションへリンクは次のとおりです。


1
あなたがリンクした議論は、サーバーサイドのチャイとモカについてです。ポスターはブラウザ側のモカとチャイについて尋ねています。
エリオットフォスター

それは同じ問題ではありません。setTimeoutこの質問に例として使用した関数は、そのコールバックの誤差を持っていません。
シルヴァン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');
    });

  });

});

パーチャイが非同期モカ・テストでの作業を取得する方法はありますか?これをNPMパッケージとして公開しました。

詳細については、https://github.com/niftylettuce/check-chaiを参照してください



1

Jean Vincentの回答に非常に関連し、それに触発されて、彼のcheck関数と同様のヘルパー関数を使用しますが、eventually代わりにそれを呼び出します(これは、chai-as-promisedの命名規則と一致するのに役立ちます)。任意の数の引数を取り、それらを元のコールバックに渡す関数を返します。これにより、テストでネストされた余分な関数ブロックを排除し、あらゆるタイプの非同期コールバックを処理できるようになります。ここにそれは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

これを解決するための繰り返しの回答や提案されたパッケージがたくさんあることは知っていますが、上記の単純なソリューションが2つのユースケースの簡潔なパターンを提供するのを見たことがありません。私はこれをコピーパスタを希望する他の人のための統合された答えとして投稿しています:

イベントコールバック

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/によって提供されるこのリンクに基づいて、describeは、完了を省略した場合に返されたPromiseを使用できます。パラメータ。

欠点は、非同期関数ではなく、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
}

この非同期関数はWebクライアント(この場合はSlacks SDK)を使用します。SDKは、API呼び出しの非同期性を処理し、ペイロードを返します。次にexpect、async promiseで返されたオブジェクトに対して実行することにより、chai内のペイロードをテストできます。

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で、Sinon'sLibraryのfakeTimerでした。必要に応じて、テストでタイマーを進めるだけです。

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

テストをより早く完了するという追加のボーナスがあります。


1
非同期コードをテストするとき、私は間違いなくこのようなソリューションを主に使用していることに気づきました。(上記のJean Vincentの回答に示されているように)コールバックMochaを「完了」させるのは良いことですが、テストは通常​​、使用しない方が簡単に記述できます。
トーマスパースロー2015

-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
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.