IT관련/Angular

Angular/Jasmine 테스트에서 done()이 왜 필요한가?

파란하늘999 2025. 11. 27. 16:33

문제 상황

it('이렇게 쓰면 무조건 실패합니다', () => {
  // done 파라미터가 없어요!
  service.getData().subscribe(data => {
    expect(data).toBe('hello');
  });
});

→ 실행하면 5초 뒤에 이런 에러가 나와요

Error: Timeout - Async callback was not invoked within 5000 ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)

왜 이런 일이 생길까?

Jasmine은 기본적으로 동기 코드만 기다립니다.

it() 블록 안의 모든 코드가 끝나면 바로 테스트를 종료했다고 판단해요.

그런데 Observable, setTimeout, HttpClient 등은 비동기라서

subscribe를 호출한 순간은 바로 끝나버립니다.

실제 값이 오는 건 훨씬 나중이죠.

결과적으로 Jasmine 입장에서는

“아, 이 테스트는 이미 다 끝났네?” → 5초 기다리다 포기 → 실패!

해결책 = Jasmine에게 “내가 끝낼 때까지 기다려!”라고 말해주기

it('done을 써야 정상 종료됩니다', (done) => {   // ← 여기서 받음
  service.getData().subscribe({
    next: data => expect(data).toBe('hello'),
    error: err => { fail(err); done(); },
    complete: () => {
      done();                     // ← 여기서 직접 종료 신호 줌
    }
  });
});

done()을 반드시 넣어야 하는 곳

it('정석 패턴', (done) => {
  observable$.subscribe({
    error: err => { fail(err); done(); },     // 에러 나도 종료
    complete: () => done()                   // 정상 완료 시 종료 (제일 중요!)
  });
});

실무에서 진짜 많이 보는 실수 Top 3

  1. done 파라미터는 있는데 호출을 깜빡 → 영원히 Pending
  2. next 안에만 done() 호출 → complete 전에 끝나면 실패
  3. error 핸들링 없이 done()만 complete에 → 에러 시 타임아웃

2025년 기준 추천 (done()은 이제 거의 안 씁니다)

// 최고로 깔끔하고 안전한 방법
it('fakeAsync + tick/flush 가 국룰', fakeAsync(() => {
  service.getData().subscribe(data => {
    expect(data).toBe('hello');
  });

  tick();        // 또는 tick(1000)
  // flush();    // 모든 비동기 한 번에 처리
  // → done() 전혀 필요 없음!
}));

한눈에 정리 표

테스트 방식 done() 필요? 비고
일반 subscribe 필요 complete와 error에 모두 호출
fakeAsync + tick 불필요 2025년 현재 99% 이걸 씀
waitForAsync 불필요 Angular 공식 문서 스타일
marble testing 불필요 TestScheduler 사용

결론

  • done() = “Jasmine아, 이 테스트는 내가 직접 끝낸다고 말할 때까지 기다려줘!”
  • 2025년에는 fakeAsync 쓰세요. done()은 레거시 코드 아니면 거의 안 써요
  • 그래도 가끔 서드파티 라이브러리나 레거시 테스트 볼 때는 여전히 등장합니다
반응형