Mocha / Chai mengharapkan.to.throw tidak menangkap kesalahan yang dilemparkan

258

Saya mengalami masalah saat membuat Chai expect.to.throwbekerja dalam pengujian untuk aplikasi node.js saya. Tes terus gagal pada kesalahan yang dilemparkan, tetapi Jika saya membungkus test case dalam mencoba dan menangkap dan menegaskan kesalahan yang tertangkap, itu berfungsi.

Tidak expect.to.throwberfungsi seperti yang saya pikir seharusnya atau sesuatu?

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

Kesalahan:

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.
doremi
sumber

Jawaban:

339

Anda harus meneruskan fungsi ke expect. Seperti ini:

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

Cara Anda melakukannya, Anda melewati ke expectdalam hasil memanggil model.get('z'). Tetapi untuk menguji apakah sesuatu dilemparkan, Anda harus melewati fungsi expect, yang expectakan memanggil dirinya sendiri. The bindMetode yang digunakan di atas menciptakan fungsi baru yang saat dipanggil akan memanggil model.getdengan thisset dengan nilai modeldan argumen set pertama untuk 'z'.

Penjelasan yang bagus binddapat ditemukan di sini .

Louis
sumber
Saya lulus fungsi kan? modelMisalnya memiliki fungsi yang disebut get yang saya lewati / panggil di harapkan.
doremi
Tidak, lihat penjelasan yang saya tambahkan saat Anda menulis komentar.
Louis
47
Oof. Mengapa docs ( chaijs.com/api/bdd/#throw ) tidak menunjukkan penggunaan bind ini? Sepertinya skenario pengujian yang paling umum to.throwadalah menguji kondisi tertentu dalam suatu fungsi, yang mengharuskan pemanggilan fungsi tersebut dengan keadaan / argumen yang tidak valid. (Dalam hal ini .... mengapa tidak deeplink chaijs.com sebenarnya deeplink?)
ericsoco
Ketika Anda melewati beberapa parameter yang tidak boleh dilempar, tes ini tetap lulus.
Alexandros Spyropoulos
6
Perhatikan ini tidak akan (pada Sep 2017) berfungsi untuk fungsi async: lihat github.com/chaijs/chai/issues/882#issuecomment-322131680 dan diskusi terkait.
ChrisV
175

Seperti yang dikatakan jawaban ini , Anda juga bisa membungkus kode Anda dalam fungsi anonim seperti ini:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
Twiz
sumber
7
Ini tidak berfungsi untuk panggilan fungsi tidak sinkron. Misalkan model.get adalah async yang mengembalikan janji. Namun itu melempar kesalahan. Jika saya mencoba pendekatan di atas, itu adalah "Waktu" karena kita harus memberi tahu "selesai" dengan moka. Pada saat yang sama, saya tidak dapat mencoba expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); Karena tidak ada metode pemberitahuan.
Anand N
@ AnandN Jika saya memahami masalah Anda, ini terdengar seperti Anda hanya perlu memperbaiki kode Anda untuk menangani kesalahan. Wont kesalahan unhandled dalam fungsi async menjadi masalah di aplikasi Anda yang sebenarnya juga?
twiz
2
Twiz terima kasih atas balasan Anda. Kami bekerja di lingkungan yang terintegrasi, modul penggunaan menangani penangkapan pengecualian. Jadi, masalahnya adalah ketika kita mencoba menjalankan unit test case. Akhirnya kami menggunakan pendekatan di bawah ini untuk membuatnya bekerja catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N
1
Solusi bagus kecuali saat Anda menggunakan thisfungsi yang akan dipanggil. Lalu .bindadalah cara yang tepat untuk pergi.
rabbitco
@AnandN Panggilan fungsi asinkron tidak melempar , ia menolak s. Untuk referensi di masa mendatang, chai-as-janji menangani ini dengan cukup baik.
user5532169
85

Dan jika Anda sudah menggunakan ES6 / ES2015 maka Anda juga dapat menggunakan fungsi panah. Pada dasarnya sama dengan menggunakan fungsi anonim normal tetapi lebih pendek.

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
Daniel T.
sumber
TIDAK BISA ada masalah dengan ini karena fungsi panah mengambil lingkup sekitarnya untukthis
Eric Hodonsky
1
@ Relic Ya, sangat benar. Ini juga bisa menjadi keuntungan besar dari fungsi panah. Fungsi panah 'mewarisi' thisdari ruang lingkup tempat mereka dibuat. Sering kali ini bisa menjadi keuntungan, karena menghindari kebutuhan untuk memasukkan bindfungsi ke thisobjek mereka secara manual.
Stijn de Witt
@StijndeWitt ini bukan keuntungan atau kerugian, itu kontrol ruang lingkup dan disengaja. Ini sebenarnya sintaks gula untuk digunakanbind dan selalu terikat thispada lingkup induknya. Niat saya dalam komentar itu hanya untuk memastikan pembaca menyadari potensi jatuhan.
Eric Hodonsky
1
@ Relic Ya saya setuju dengan Anda. Ini dapat digunakan untuk keuntungan dan bisa menjadi alasan yang baik untuk menggunakan fungsi panah.
Stijn de Witt
75

Pertanyaan ini memiliki banyak, banyak duplikat, termasuk pertanyaan yang tidak menyebutkan perpustakaan pernyataan Chai. Berikut adalah dasar-dasar yang dikumpulkan bersama:

Pernyataan harus memanggil fungsi, alih-alih segera mengevaluasi.

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

Anda dapat memeriksa kesalahan tertentu menggunakan pustaka pernyataan apa saja:

Node

  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./)

Sebaiknya

  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./)

Chai Harapkan

  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./);

Anda harus menangani pengecualian yang 'lolos' dari ujian

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

Ini bisa terlihat membingungkan pada awalnya. Seperti mengendarai sepeda, itu hanya 'klik' selamanya setelah diklik.

Charles Merriam
sumber
15

contoh dari doc ...;)

karena Anda mengandalkan thiskonteks:

  • yang hilang saat fungsi dipanggil oleh .throw
  • tidak mungkin untuk mengetahui apa yang seharusnya terjadi

Anda harus menggunakan salah satu opsi ini:

  • bungkus panggilan metode atau fungsi di dalam fungsi lain
  • ikat konteksnya

    // 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
Michal Miky Jankovský
sumber
Ini adalah bagaimana saya melakukannya juga. Saya menemukan, bahwa implementasi ES6 sejauh ini adalah yang paling mudah dibaca
relief.melone
1

Satu implementasi lain yang mungkin, lebih rumit daripada solusi .bind (), tetapi yang membantu untuk membuat titik yang diharapkan () membutuhkan fungsi yang menyediakan thiskonteks ke fungsi yang dicakup, Anda dapat menggunakan call(), misalnya,

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

SeanOlson
sumber
0

Saya telah menemukan cara yang bagus di sekitarnya:

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

Ini jauh lebih mudah dibaca daripada versi lama saya:

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
});
Dani-Br
sumber