Có cách nào để Chai hoạt động với các bài kiểm tra Mocha không đồng bộ không?


81

Tôi đang chạy một số kiểm tra không đồng bộ trong Mocha bằng Trình chạy trình duyệt và tôi đang cố gắng sử dụng các xác nhận kiểu mong đợi của Chai:

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

Điều này không cung cấp cho tôi thông báo xác nhận thất bại bình thường, thay vào đó tôi nhận được:

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)

Vì vậy, rõ ràng là nó đang bắt lỗi, chỉ là nó không hiển thị chính xác. Bất kỳ ý tưởng làm thế nào để làm điều này? Tôi đoán tôi chỉ có thể gọi là "xong" với một đối tượng lỗi nhưng sau đó tôi mất tất cả vẻ sang trọng của một thứ như Chai và nó trở nên rất khó nghe ...


Vấn đề là với mocha phía trình duyệt. Xem github.com/visionmedia/mocha/pull/278 để biết thông tin về điều này.
Elliot Foster

Kể từ năm 2020, bạn nên xem qua chai-as-promisedplugin ...
Elmar Zander

Câu trả lời:


96

Kiểm tra không đồng bộ của bạn tạo ra một ngoại lệ, trên các expect()ations bị lỗi , không thể được nắm bắt bởi it()vì ngoại lệ được ném ra ngoài it()phạm vi của 's.

Ngoại lệ đã chụp mà bạn thấy được hiển thị được ghi lại bằng cách sử dụng process.on('uncaughtException')dưới nút hoặc sử dụng window.onerror()trong trình duyệt.

Để khắc phục sự cố này, bạn cần nắm bắt ngoại lệ trong hàm không đồng bộ được gọi bởi setTimeout()để gọi done()ngoại lệ là tham số đầu tiên. Bạn cũng cần gọi done()mà không có tham số nào để cho biết thành công, nếu không mocha sẽ thông báo lỗi hết thời gian chờ vì chức năng kiểm tra của bạn sẽ không bao giờ báo hiệu rằng nó đã được thực hiện:

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

Làm như vậy trên tất cả các trường hợp thử nghiệm của bạn là khó chịu và không KHÔ, vì vậy bạn có thể muốn cung cấp một chức năng để làm điều này cho bạn. Hãy gọi hàm này check():

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

Với check()bây giờ bạn có thể viết lại bài kiểm tra không đồng bộ của bạn như sau:

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

Tôi vừa xóa nhận xét trước đó của mình sau khi tôi nhận ra rằng phần tôi đang phàn nàn (setTimeout) thực sự là từ câu hỏi của tôi. Lấy làm tiếc!!
Thomas Parslow

2
Câu trả lời trên có vẻ sai. Một kỳ vọng không thành công sẽ ném ngay lập tức và dừng thử nghiệm với một lỗi có ý nghĩa, không cần phải thử / bắt phức tạp. Tôi vừa thử nghiệm nó ngay bây giờ bằng một thử nghiệm trình duyệt.
Offirmo

3
Tôi đã phải vật lộn với vấn đề này và tìm thấy bài blog này cực kỳ hữu ích: staxmanade.com/2015/11/...
RichardForrester

1
@RichardForrester, bài đăng cực kỳ hữu ích. Cảm ơn! Để làm cho việc kiểm tra này hoạt động với Promises, mã sẽ đơn giản hóa một cách đáng kinh ngạc. Nhưng nó phải đi kèm với các lời hứa (không phải bất kỳ hàm không đồng bộ nào).
Pedro R.

1
Chỉ muốn nói với hậu thế rằng vấn đề chính xác này xảy ra với Vue nexttick () (là một trình bao bọc cho lời hứa) và có thể được xử lý theo cách tương tự.
Eli Albert

20

Đây là bài kiểm tra vượt qua của tôi cho các hứa hẹn ES6 / ES2015 và ES7 / ES2016 async / await. Hy vọng điều này cung cấp một câu trả lời cập nhật tốt đẹp cho bất kỳ ai nghiên cứu chủ đề này:

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. Tôi đã thay đổi thành loại bỏ hoàn thành khỏi kiểm tra lời hứa. Như bạn đã chỉ ra, nó không cần thiết.
RichardForrester

13

Nếu bạn thích đã hứa, hãy thử Chai as Promised + Q , cho phép những thứ như sau:

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

2

Tôi đã hỏi điều tương tự trong danh sách gửi thư Mocha. Về cơ bản, họ đã nói với tôi điều này: để viết kiểm tra không đồng bộ với Mocha và Chai:

  • luôn bắt đầu bài kiểm tra với if (err) done(err);
  • luôn kết thúc bài kiểm tra bằng done().

Nó đã giải quyết được vấn đề của tôi và không thay đổi một dòng mã nào của tôi ở giữa (Chai kỳ vọng giữa các dòng khác). Đây setTimoutkhông phải là cách để thực hiện kiểm tra không đồng bộ.

Đây là liên kết đến cuộc thảo luận trong danh sách gửi thư .


1
Cuộc thảo luận mà bạn đã liên kết là về chai và mocha phía máy chủ. Người đăng đang hỏi về mocha và chai phía trình duyệt .
Elliot Foster

Đó không phải là vấn đề tương tự. Các setTimeoutchức năng sử dụng như ví dụ trong câu hỏi này không có bất kỳ lỗi trong callback của nó.
Sylvain B

1

Tôi đã xuất bản một gói giải quyết vấn đề này.

Đầu tiên cài đặt check-chaigói:

npm install --save check-chai

Sau đó, trong các thử nghiệm của bạn, hãy sử dụng chai.use(checkChai);và sau đó sử dụng chai.checkchức năng trợ giúp như hình dưới đây:

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 Có cách nào để Chai làm việc với các bài kiểm tra Mocha không đồng bộ không? Tôi đã xuất bản điều này dưới dạng gói NPM.

Vui lòng xem https://github.com/niftylettuce/check-chai để biết thêm thông tin.



1

Rất liên quan và lấy cảm hứng từ câu trả lời của Jean Vincent , chúng tôi sử dụng một chức năng trợ giúp tương tự như chức năng của anh ấy check, nhưng chúng tôi gọi nó eventuallythay thế (điều này giúp nó phù hợp với quy ước đặt tên của chai-as-hứa). Nó trả về một hàm nhận bất kỳ số lượng đối số nào và chuyển chúng đến lệnh gọi lại ban đầu. Điều này giúp loại bỏ một khối chức năng lồng nhau bổ sung trong các thử nghiệm của bạn và cho phép bạn xử lý bất kỳ loại gọi lại không đồng bộ nào. Ở đây nó được viết bằng ES2015:

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

Cách sử dụng ví dụ:

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

Tôi biết có nhiều câu trả lời lặp lại và các gói gợi ý để giải quyết vấn đề này tuy nhiên tôi không thấy các giải pháp đơn giản ở trên cung cấp một mẫu ngắn gọn cho hai trường hợp sử dụng. Tôi đăng bài này như một câu trả lời tổng hợp cho những người muốn sao chép mì ống:

sự kiện gọi lại

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

gọi lại kiểu nút

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

ví dụ sử dụng

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

Dựa trên liên kết này được cung cấp bởi @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/ , mô tả có thể sử dụng Lời hứa được trả lại nếu bạn bỏ qua việc hoàn thành tham số.

Chỉ có nhược điểm là phải có một Lời hứa ở đó, không phải bất kỳ chức năng không đồng bộ nào (bạn có thể bọc nó bằng một Lời hứa, bạn). Nhưng trong trường hợp này, mã có thể được giảm rất nhiều.

Nó có tính đến các lỗi trong hàm funcThatReturnsAPromise ban đầu hoặc các kỳ vọng:

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

0

Tôi đã giải quyết nó khi giải nén try/catchthành một hàm.

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

Sau đó, it()tôi gọi:

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

        });

Nó cũng có thể gỡ lỗi.


0

Bộ hẹn giờ trong quá trình kiểm tra và không đồng bộ có vẻ khá khó khăn. Có một cách để làm điều này với cách tiếp cận dựa trên lời hứa.

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

Hàm không đồng bộ này sử dụng một máy khách Web (trong trường hợp này là Slacks SDK). SDK sẽ xử lý tính chất không đồng bộ của lệnh gọi API và trả về tải trọng. Sau đó, chúng tôi có thể kiểm tra tải trọng bên trong chai bằng cách chạy expectvới đối tượng được trả về trong lời hứa không đồng bộ.

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

Điều hoạt động rất hiệu quả đối với tôi icm Mocha / Chai là fakeTimer từ Thư viện của Sinon. Chỉ cần nâng cao bộ đếm thời gian trong thử nghiệm khi cần thiết.

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

Có thêm phần thưởng là hoàn thành bài kiểm tra nhanh hơn.


1
Tôi chắc chắn thấy mình chủ yếu sử dụng các giải pháp như thế này bây giờ khi kiểm tra mã không đồng bộ. Thật tốt khi gọi lại Mocha "xong" (như trong câu trả lời của Jean Vincent ở trên) nhưng các bài kiểm tra thường dễ viết hơn khi bạn không sử dụng nó.
Thomas Parslow

-2

Bạn cũng có thể sử dụng mô-đun miền. Ví dụ:

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
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.