Async/Await를 사용하여 Promise 부작용 테스트

일부 프레임워크의 콜백 내에서 비동기 코드를 호출하고 그 부작용을 테스트해야 하는 상황에 처했을 수 있습니다. 예를 들어, React 구성 요소 내부에서 API 호출을 할 수 있습니다. componentDidMount() 차례로 호출할 콜백 setState() 요청이 완료되고 구성 요소가 특정 상태에 있다고 주장하려는 경우. 이 문서에서는 이러한 유형의 시나리오를 테스트하는 기술을 보여줍니다.
간단한 예를 들어보세요. 우리는 수업이 있습니다 PromisesHaveFinishedIndicator. 생성자는 약속 목록을 받습니다. 모든 약속이 해결되면 인스턴스의 finished 속성이로 설정되었습니다. true:

클래스 PromisesHaveFinishedIndicator { 생성자(약속) { this.finished = false; Promise.all(promises).then(() => { this.finished = true; }); } }

좋은 테스트 사례에는 해결 타이밍을 제어할 수 있는 여러 Promise를 사용하여 생성자를 호출하고 값에 대한 기대치를 작성하는 것이 포함됩니다. this.finished 각 약속이 해결될 때마다.
테스트에서 약속의 해결 시기를 제어하기 위해 다음을 사용합니다. Deferred 노출시키는 물체 resolvereject 방법 :

class Deferred { 생성자() { this.promise = new Promise((해결, 거부) => { this.resolve = 해결; this.reject = 거부; }); } }

이를 통해 우리는 다음에 대한 테스트를 설정할 수 있습니다. PromisesHaveFinishedIndicator. 우리는 농담 이 예에서는 테스트 프레임워크를 사용하지만 이 기술은 다른 테스트 프레임워크에도 적용될 수 있습니다.

test('모든 프로미스가 해결된 후 true로 설정 완료', () => { const d1 = new Deferred(); const d2 = new Deferred(); const 표시자 = new PromisesHaveFinishedIndicator([d1.promise, d2.promise] ); 기대(indicator.finished).toBe(false); d2.resolve(); 기대(indicator.finished).toBe(false); d1.resolve(); 기대(indicator.finished).toBe(true); });

Promise 콜백은 비동기식이므로 이 테스트는 실제로 실패합니다. 따라서 대기열에 있는 모든 콜백은 다음으로 인해 이 테스트의 마지막 문 이후에 실행됩니다. 끝까지 달려가다 의미론. 즉, 다음에 대한 약속 콜백은 Promise.all 요구: () => { this.finished = true; } 이 테스트가 이미 종료된 후에 실행됩니다!
Jest(및 기타 테스트 프레임워크)는 마지막 문 이후에 테스트가 종료되는 것을 방지하여 비동기성을 처리하는 방법을 제공합니다. 우리는 제공된 전화를 호출해야 할 것입니다 done 테스트가 완료되었음을 러너에게 알리기 위한 함수입니다. 이제 다음과 같은 것이 작동할 것이라고 생각할 수 있습니다.

test('모든 프로미스가 해결된 후 true로 설정 완료', (done) => { const d1 = new Deferred(); const d2 = new Deferred(); const 표시자 = new PromisesHaveFinishedIndicator([d1.promise, d2.promise ]); 기대(indicator.finished).toBe(false); d2.resolve(); d2.then(() => { 기대(indicator.finished).toBe(false); d1.resolve(); d1. then(() => { Expect(indicator.finished).toBe(true); done(); }); }); });

그러나 이것도 실패합니다. 그 이유는 구현에 있습니다. Promise.all. 언제 resolve 호출된다 d1 (그리고 d2 또한), Promise.all 모든 Promise가 해결되었는지 확인하는 콜백을 예약합니다. 이 검사가 true를 반환하면 Promise.all 호출하면 대기열에 추가됩니다. () => { this.finished = true; } 콜백. 이 콜백은 그때쯤에는 여전히 대기열에 남아 있습니다. done 라고!
이제 문제는 설정하는 콜백을 어떻게 만드는가입니다. this.finishedtrue 전화하기 전에 달려가다 done? 이에 답하려면 Promise가 해결되거나 거부될 때 Promise 콜백이 어떻게 예약되는지 이해해야 합니다. Jake Archibald의 기사 작업, 마이크로 작업, 대기열 및 일정 해당 주제에 대해 자세히 설명하고 있으므로 읽어 보시기를 적극 권장합니다.
요약하면 Promise 콜백은 마이크로태스크 대기열과 다음과 같은 API의 콜백에 대기됩니다. setTimeout(fn)setInterval(fn) 매크로태스크 대기열에 대기합니다. 마이크로태스크 대기열에 있는 콜백은 스택이 비워진 직후에 실행되며, 마이크로태스크가 다른 마이크로태스크를 예약하는 경우 매크로태스크 대기열로 양보되기 전에 계속해서 대기열에서 제거됩니다.
이 지식을 바탕으로 다음을 사용하여 이 테스트를 통과할 수 있습니다. setTimeout 대신 then():

test('모든 프로미스가 해결된 후 true로 설정 완료', (done) => { const d1 = new Deferred(); const d2 = new Deferred(); const 표시자 = new PromisesHaveFinishedIndicator([d1.promise, d2.promise ]); 기대(indicator.finished).toBe(false); d2.resolve(); setTimeout(() => { 기대(indicator.finished).toBe(false); d1.resolve(); setTimeout(() => { 기대(indicator.finished).toBe(true); 완료(); }, 0); }, 0); });

이것이 작동하는 이유는 setTimeout 콜백이 실행되면 다음 Promise 콜백이 실행되었음을 알 수 있습니다.

  • 구현 내부의 콜백 Promise.all 모든 Promise가 해결되었는지 확인한 다음 반환된 Promise를 해결합니다.
  • 설정하는 콜백 this.finished = true.

잔뜩 가지고 있는 setTimeout(fn, 0) 우리 코드에서는 아무리 말해도 보기 흉합니다. 우리는 이것을 새로운 것으로 청소할 수 있습니다 async/await 통사론:

function flashPromises() { return new Promise((resolve, Reject) => setTimeout(resolve, 0)); } test('모든 프로미스가 해결된 후 true로 설정 완료', async () => { const d1 = new Deferred(); const d2 = new Deferred(); const 표시자 = new PromisesHaveFinishedIndicator([d1.promise, d2. promise]); Expect(indicator.finished).toBe(false); d2.resolve(); wait pushPromises(); Expect(indicator.finished).toBe(false); d1.resolve(); wait pushPromises(); 기대(indicator.finished).toBe(true); });

좀 더 화려하게 만들고 싶다면 다음을 사용할 수 있습니다. setImmediate 대신 setTimeout 일부 환경(Node.js)에서는 보다 빠르다 setTimeout 그러나 마이크로태스크 후에도 여전히 실행됩니다.

function flashPromises() { return new Promise(resolve => setImmediate(resolve)); }

약속 및 비동기성과 관련된 테스트를 작성할 때 콜백이 예약되는 방식과 이벤트 루프에서 다양한 대기열이 수행하는 역할을 이해하는 것이 좋습니다. 이러한 지식을 가지면 우리가 작성하는 비동기 코드를 추론할 수 있습니다.

비슷한 게시물