มอคค่า / ชัยคาดว่าจะไม่โยนข้อผิดพลาดที่ส่งออกไป


257

ฉันมีปัญหาในการให้ Chai expect.to.throwทำงานทดสอบแอพ node.js ของฉัน การทดสอบล้มเหลวในข้อผิดพลาดที่ส่งออกมา แต่ถ้าฉันปิดกรณีทดสอบเพื่อลองจับและยืนยันข้อผิดพลาดที่ตรวจพบ

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

วิธีที่คุณกำลังทำมันคุณจะผ่านไปผลของการโทร แต่เพื่อทดสอบว่ามีบางอย่างถูกโยนหรือไม่คุณต้องผ่านฟังก์ชั่นไปซึ่งจะเรียกตัวเองว่า ใช้วิธีการดังกล่าวข้างต้นสร้างฟังก์ชั่นใหม่ที่เรียกว่าเมื่อจะเรียกกับชุดค่าของและเป็นครั้งแรกชุดอาร์กิวเมนต์expectmodel.get('z')expectexpectbindmodel.getthismodel'z'

คำอธิบายที่ดีของbindสามารถพบได้ที่นี่


ฉันผ่านฟังก์ชั่นใช่หรือไม่ modelตัวอย่างมีฟังก์ชั่นที่เรียกว่ารับที่ฉันผ่าน / เรียกว่าคาดหวัง
doremi

ไม่เห็นคำอธิบายที่ฉันเพิ่มในขณะที่คุณเขียนความคิดเห็นของคุณ
หลุยส์

47
OOF เหตุใดเอกสาร ( chaijs.com/api/bdd/#throw ) จึงไม่แสดงการผูกนี้ ดูเหมือนว่าสถานการณ์การทดสอบที่พบบ่อยที่สุดสำหรับto.throwการทดสอบเงื่อนไขเฉพาะภายในฟังก์ชั่นซึ่งต้องเรียกฟังก์ชั่นที่มีสถานะ / ข้อโต้แย้งที่ไม่ถูกต้อง (สำหรับเรื่องนั้น .... ทำไม deeplink ของ daiplink ถึงไม่จริง ๆ deeplink ล่ะ?)
ericsoco

เมื่อคุณผ่านพารามิเตอร์บางอย่างที่ไม่ควรโยนการทดสอบก็ยังผ่าน
Alexandros Spyropoulos

6
โปรดทราบว่าสิ่งนี้จะไม่ทำงาน (เมื่อวันที่ Sep 2017) สำหรับฟังก์ชั่น async: ดูgithub.com/chaijs/chai/issues/882#issuecomment-322131680และการสนทนาที่เกี่ยวข้อง
ChrisV

175

ตามที่คำตอบนี้บอกไว้คุณสามารถห่อโค้ดของคุณในฟังก์ชั่นนิรนามดังนี้

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

7
สิ่งนี้ไม่ทำงานสำหรับการเรียกใช้ฟังก์ชันอะซิงโครนัส สมมติว่า model.get เป็น async ที่คืนค่าสัญญา อย่างไรก็ตามมันจะพ่นข้อผิดพลาด ถ้าฉันลองใช้วิธีข้างต้นมันคือ "หมดเวลา" เนื่องจากเราต้องแจ้ง "เสร็จสิ้น" กับมอคค่า ในเวลาเดียวกันฉันไม่สามารถลองได้expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); เนื่องจากไม่มีวิธีการแจ้งเตือน
Anand N

@AnandN หากฉันเข้าใจปัญหาของคุณดูเหมือนว่าคุณแค่ต้องปรับรหัสของคุณใหม่เพื่อจัดการกับข้อผิดพลาด ข้อผิดพลาดที่ไม่สามารถจัดการได้ในฟังก์ชั่น async จะเป็นปัญหาในแอปจริงของคุณด้วยหรือไม่?
twiz

2
ขอบคุณ twiz สำหรับคำตอบของคุณ เรากำลังทำงานในสภาพแวดล้อมแบบบูรณาการโมดูลที่ใช้ดูแลการจับข้อยกเว้น ดังนั้นปัญหาคือเมื่อเราพยายามเรียกใช้กรณีทดสอบหน่วย ในที่สุดเราก็ใช้วิธีการด้านล่างเพื่อให้มันใช้งานได้ catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N

1
ทางออกที่ดียกเว้นเมื่อคุณใช้งานthisภายในฟังก์ชั่นที่จะเรียกใช้ จากนั้น.bindเป็นวิธีที่เหมาะสมที่จะไป
rabbitco

@AnandN ฟังก์ชั่นการโทรแบบอะซิงโครนัสไม่ได้โยนแต่จะปฏิเสธ s สำหรับการอ้างอิงในอนาคตการจัดการตามสัญญาแบบนี้ค่อนข้างดี
user5532169

85

และถ้าคุณใช้ ES6 / ES2015 อยู่แล้วคุณสามารถใช้ฟังก์ชันลูกศรได้ โดยพื้นฐานแล้วมันเหมือนกับการใช้ฟังก์ชั่นนิรนามทั่วไป แต่สั้นกว่า

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

อาจมีปัญหากับสิ่งนี้เพราะฟังก์ชั่นลูกศรใช้ขอบเขตรอบตัวสำหรับthis
Eric Hodonsky

1
@Relic ใช่จริงมาก นี่อาจเป็นข้อได้เปรียบใหญ่ของฟังก์ชั่นลูกศร ฟังก์ชั่นลูกศร 'สืบทอด' thisจากขอบเขตที่สร้างขึ้นบ่อยครั้งสิ่งนี้อาจเป็นข้อได้เปรียบเนื่องจากไม่จำเป็นต้องใช้bindฟังก์ชั่นในthisวัตถุด้วยตนเอง
Stijn de Witt

@ StijndeWitt นี้ไม่ได้เป็นข้อได้เปรียบหรือเสียเปรียบมันเป็นขอบเขตการควบคุมและเจตนา จริงๆแล้วมันคือไวยากรณ์ของน้ำตาลสำหรับการใช้งานbindและผูกกับthisขอบเขตของพาเรนต์เสมอ ความตั้งใจของฉันในการแสดงความคิดเห็นเป็นเพียงเพื่อให้แน่ใจว่าผู้อ่านได้ตระหนักถึงการตกหลุมที่อาจเกิดขึ้น
Eric Hodonsky

1
@Relic ใช่ฉันเห็นด้วยกับคุณ สามารถใช้เป็นข้อได้เปรียบและเป็นเหตุผลที่ดีในการใช้ฟังก์ชั่นลูกศร
Stijn de Witt

75

คำถามนี้มีคำถามซ้ำซ้อนมากมายรวมถึงคำถามที่ไม่ได้กล่าวถึงห้องสมุดยืนยันชัย นี่คือพื้นฐานการรวบรวมเข้าด้วยกัน:

การยืนยันต้องเรียกใช้ฟังก์ชันแทนการประเมินทันที

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 นั้นเป็นสิ่งที่อ่านได้ง่ายที่สุด
โล่งอก Felone

1

การใช้งานที่เป็นไปได้อีกอย่างหนึ่งซึ่งยุ่งยากกว่าโซลูชัน. ผูก () แต่สิ่งหนึ่งที่ช่วยทำให้จุดที่คาดหวัง () ต้องใช้ฟังก์ชันที่จัดเตรียม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.