Chai가 비동기 Mocha 테스트로 작업하게하는 방법이 있습니까?


81

Browser Runner를 사용하여 Mocha에서 비동기 테스트를 실행하고 있으며 Chai의 기대 스타일 어설 션을 사용하려고합니다.

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)

따라서 분명히 오류를 포착하고 있으며 올바르게 표시하지 않습니다. 이 작업을 수행하는 방법에 대한 아이디어가 있습니까? 오류 객체를 사용하여 "done"이라고 부를 수 있지만 Chai와 같은 것의 모든 우아함을 잃고 매우 투박해집니다.


문제는 브라우저 측 모카에 있습니다. 이에 대한 정보는 github.com/visionmedia/mocha/pull/278 을 참조하십시오 .
Elliot Foster

2020 년부터 chai-as-promised플러그인을 살펴
보셔야합니다

답변:


96

비동기 테스트는 실패한 경우 예외가 의 범위 외부로 throw expect()되어 캡처 할 수없는 예외를 생성합니다 .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
위의 대답은 잘못된 것 같습니다. 실패한 예상은 즉시 발생하고 의미있는 오류로 테스트를 중지하므로 복잡한 try / catch가 필요하지 않습니다. 방금 브라우저 테스트로 테스트했습니다.
Offirmo

3
나는이 문제로 어려움을 겪고 있었고이
RichardForrester

1
@RichardForrester, 매우 유용한 게시물입니다. 감사! 이 검사를 Promises와 함께 사용하면 코드가 매우 단순화됩니다. 그러나 그것은 (비동기 함수가 아닌) promise와 함께 있어야합니다.
Pedro R.

1
이 정확한 문제가 Vue nexttick () (Promise의 래퍼)에서 발생하고 같은 방식으로 처리 될 수 있다는 사실을 후대에 차임하고 싶습니다.
엘리 알버트

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. 약속 테스트에서 완료를 제거하도록 변경했습니다. 지적했듯이 필요하지 않습니다.
RichardForrester 2016 년


2

나는 Mocha 메일 링리스트에서 같은 것을 물었다. 그들은 기본적으로 이렇게 말했습니다. Mocha와 Chai로 비동기 테스트를 작성하기 위해 :

  • 항상 테스트 시작 if (err) done(err);
  • 항상 done().

그것은 내 문제를 해결했고 사이에 내 코드의 한 줄을 변경하지 않았습니다 (Chai 기대치). 이것은 setTimout비동기 테스트를 수행하는 방법이 아닙니다.

다음 은 메일 링리스트에있는 토론 링크 입니다.


1
링크 한 토론은 서버 측 차이와 모카에 관한 것입니다. 포스터는 브라우저 쪽 모카와 차이 에 대해 묻고 있습니다.
Elliot Foster

그것은 같은 문제가 아닙니다. setTimeout이 질문에서 예제로 사용 된 함수는 콜백에 오류가 없습니다.
Sylvain 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');
    });

  });

});

Per Chai가 비동기 Mocha 테스트로 작업하게하는 방법이 있습니까? 나는 이것을 NPM 패키지로 게시했습니다.

자세한 내용은 https://github.com/niftylettuce/check-chai 를 참조하십시오.



1

Jean Vincent의 답변 과 매우 관련이 있고 영감을 받아 그의 check함수 와 유사한 도우미 함수를 사용 하지만 eventually대신 호출합니다 (이것은 약속 된 차이의 명명 규칙과 일치하는 데 도움이됩니다). 임의의 수의 인수를 사용하여 원래 콜백에 전달하는 함수를 반환합니다. 이를 통해 테스트에서 추가 중첩 함수 블록을 제거하고 모든 유형의 비동기 콜백을 처리 할 수 ​​있습니다. 여기 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

이 문제를 해결하기 위해 많은 반복 답변과 제안 된 패키지가 있다는 것을 알고 있지만 위의 간단한 솔루션이 두 가지 사용 사례에 대해 간결한 패턴을 제공하는 것을 보지 못했습니다. 나는 이것을 복사-파스타를 원하는 다른 사람들을 위해 통합 답변으로 게시하고 있습니다.

이벤트 콜백

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
}

이 비동기 함수는 웹 클라이언트 (이 경우 Slacks SDK)를 사용합니다. SDK는 API 호출의 비동기 특성을 처리하고 페이로드를 반환합니다. 그런 다음 expect비동기 약속에서 반환 된 개체에 대해 실행 하여 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 's Library의 fakeTimer였습니다. 필요한 경우 테스트에서 타이머를 앞당기십시오.

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

테스트를 더 빨리 완료하는 추가 보너스가 있습니다.


1
나는 비동기 코드를 테스트 할 때 대부분 이와 같은 솔루션을 사용하고 있음을 분명히 알게되었습니다. 위의 Jean Vincent의 답변에서 볼 수 있듯이 콜백 Mocha를 "완료"하는 것이 좋지만 테스트는 일반적으로 사용하지 않을 때 작성하기가 더 쉽습니다.
Thomas Parslow 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.