モカ/チャイexpect.to.throwはスローされたエラーをキャッチしません


257

expect.to.throw私のnode.jsアプリのテストでChai を機能させるのに問題があります。スローされたエラーでテストが失敗し続けますが、テストケースをラップして、キャッチしてエラーをキャッチしてアサートすると、テストは機能します。

expect.to.throw私はそれが何かあるべきだと思うように動作しませんか?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

失敗:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.

回答:


339

関数をに渡す必要がありますexpect。このような:

expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));

あなたがそれをしている方法、あなたは呼び出しexpect結果にパスしていmodel.get('z')ます。しかし、何かがスローされているかどうかをテストするには、に機能渡す必要がありexpectexpect自分自身を呼び出しますし。bind上記使用される方法は、呼び出すと呼ばれる新しい関数作成model.getthisの値に設定modelし、最初の引数セット'z'

良い説明をbind見つけることができるここに


私は関数を渡しましたか?modelインスタンスには、私が渡した/期待して呼び出したgetという関数があります。
ドレミ2014

いいえ、コメントを書き込んでいる間に追加した説明を参照してください。
ルイ

47
ウーフ。ドキュメント(chaijs.com/api/bdd/#throw)がこのバインドの使用法を実証しないのはなぜですか?の最も一般的なテストシナリオのように思われるのto.throwは、関数内の特定の条件をテストすることです。これには、無効な状態/引数でその関数を呼び出す必要があります。(それについては.... chaijs.comのディープリンクが実際にディープリンクにならないのはなぜですか?)
ericsoco 14

スローしてはいけないパラメータを渡しても、テストは合格です。
Alexandros Spyropoulos 2014年

6
これは(2017年9月現在)非同期機能では機能しないことに注意してください。github.com / chaijs / chai / issues / 882issuecomment-322131680および関連する説明を参照してください。
ChrisV

175

この答えは言う、あなたはまた、ちょうどこのような匿名関数でコードをラップすることができます。

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');

7
これは非同期関数呼び出しでは機能しません。model.getがpromiseを返す非同期であるとします。ただし、エラーがスローされます。上記の方法を試した場合、モカに「完了」を通知する必要があるため、「タイムアウト」になります。同時に、expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); 通知方法がないので試せません。
Anand N

@AnandN私が問題を理解している場合、エラーを処理するにはコードをリファクタリングする必要があるようです。非同期関数の未処理エラーは、実際のアプリでも問題になりますか?
twiz 2015

2
twizさん、返信ありがとうございます。私たちは統合環境で作業しており、usingモジュールが例外をキャッチします。したがって、問題は単体テストケースを実行しようとするときです。最後に、以下のアプローチを使用して機能させました catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N

1
this呼び出される関数の内部を使用している場合を除いて、良い解決策です。次.bindに行くのが正しい方法です。
rabbitco 2016

@AnandN非同期関数呼び出しはスローせず、拒否します。将来の参考のために、約束通りのchaiはこれを非常にうまく処理ます。
user5532169

85

また、ES6 / ES2015をすでに使用している場合は、矢印機能も使用できます。基本的には、通常の無名関数を使用するのと同じですが、より短いです。

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');

矢印関数は周囲のスコープを使用するため、これには問題がある可能性がありますthis
Eric Hodonsky

1
@Relicはい、そうです。これは、矢印関数の大きな利点にもなります。Arrow関数thisは、それらが作成されたスコープから「継承」します。bind関数をthisオブジェクトに手動で追加する必要がなくなるため、多くの場合、これは利点になります。
Stijn de Witt

@StijndeWittこれは利点でも欠点でもありません。スコープ制御であり、意図的なものです。これは実際には、使用bindするための構文糖であり、常にthis親スコープのバインド先です。コメントの私の意図は、読者が潜在的な落とし穴に気づくことを確実にすることだけでした。
Eric Hodonsky、

1
@Relicはい私はあなたに同意します。これは有利に使用でき、アロー関数を使用する正当な理由になる場合があります。
Stijn de Witt

75

この質問には、Chaiアサーションライブラリについて言及していない質問を含め、多くの重複があります。まとめた基本事項は次のとおりです。

アサーションは、すぐに評価するのではなく、関数を呼び出す必要があります。

assert.throws(x.y.z);      
   // FAIL.  x.y.z throws an exception, which immediately exits the
   // enclosing block, so assert.throw() not called.
assert.throws(()=>x.y.z);  
   // assert.throw() is called with a function, which only throws
   // when assert.throw executes the function.
assert.throws(function () { x.y.z });   
   // if you cannot use ES6 at work
function badReference() { x.y.z }; assert.throws(badReference);  
   // for the verbose
assert.throws(()=>model.get(z));  
   // the specific example given.
homegrownAssertThrows(model.get, z);
   //  a style common in Python, but not in JavaScript

アサーションライブラリを使用して、特定のエラーを確認できます。

ノード

  assert.throws(() => x.y.z);
  assert.throws(() => x.y.z, ReferenceError);
  assert.throws(() => x.y.z, ReferenceError, /is not defined/);
  assert.throws(() => x.y.z, /is not defined/);
  assert.doesNotThrow(() => 42);
  assert.throws(() => x.y.z, Error);
  assert.throws(() => model.get.z, /Property does not exist in model schema./)

すべき

  should.throws(() => x.y.z);
  should.throws(() => x.y.z, ReferenceError);
  should.throws(() => x.y.z, ReferenceError, /is not defined/);
  should.throws(() => x.y.z, /is not defined/);
  should.doesNotThrow(() => 42);
  should.throws(() => x.y.z, Error);
  should.throws(() => model.get.z, /Property does not exist in model schema./)

チャイ期待

  expect(() => x.y.z).to.throw();
  expect(() => x.y.z).to.throw(ReferenceError);
  expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
  expect(() => x.y.z).to.throw(/is not defined/);
  expect(() => 42).not.to.throw();
  expect(() => x.y.z).to.throw(Error);
  expect(() => model.get.z).to.throw(/Property does not exist in model schema./);

テストを「エスケープ」する例外を処理する必要があります

it('should handle escaped errors', function () {
  try {
    expect(() => x.y.z).not.to.throw(RangeError);
  } catch (err) {
    expect(err).to.be.a(ReferenceError);
  }
});

これは最初は混乱しているように見えます。自転車に乗るように、一度クリックすると永遠に「クリック」します。


14

docからの例...;)

あなたはthis文脈に依存しているので:

  • 関数が.throwによって呼び出されると失われます
  • これがどうあるべきかを知る方法はありません

次のいずれかのオプションを使用する必要があります。

  • メソッドまたは関数呼び出しを別の関数内にラップする
  • コンテキストをバインドする

    // wrap the method or function call inside of another function
    expect(function () { cat.meow(); }).to.throw();  // Function expression
    expect(() => cat.meow()).to.throw();             // ES6 arrow function
    
    // bind the context
    expect(cat.meow.bind(cat)).to.throw();           // Bind

これも私のやり方です。ES6の実装は、これまでで最も読みやすいものであると、私を見つける
relief.melone

1

.bind()ソリューションよりも厄介なもう1つの可能な実装ですが、expect()がthis対象の関数にコンテキストを提供する関数を必要とするという点を理解するのに役立ちますcall()。たとえば、

expect(function() {model.get.call(model, 'z');}).to.throw('...');


0

私はそれを回避する良い方法を見つけました:

// The test, BDD style
it ("unsupported site", () => {
    The.function(myFunc)
    .with.arguments({url:"https://www.ebay.com/"})
    .should.throw(/unsupported/);
});


// The function that does the magic: (lang:TypeScript)
export const The = {
    'function': (func:Function) => ({
        'with': ({
            'arguments': function (...args:any) {
                return () => func(...args);
            }
        })
    })
};

それは私の古いバージョンよりもはるかに読みやすいです:

it ("unsupported site", () => {
    const args = {url:"https://www.ebay.com/"}; //Arrange
    function check_unsupported_site() { myFunc(args) } //Act
    check_unsupported_site.should.throw(/unsupported/) //Assert
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.