본문으로 건너뛰기

테스트

TanStack Query를 사용하는 대부분의 Angular 테스트에는 injectQuery/injectMutation을 호출하는 서비스 또는 컴포넌트가 포함됩니다.

TanStack Query의 inject* 함수는 PendingTasks와 통합되어 프레임워크가 진행 중인 쿼리와 뮤테이션을 인식하도록 보장합니다.

즉, 테스트와 SSR은 뮤테이션과 쿼리가 이행될 때까지 기다릴 수 있습니다. 단위 테스트에서는 ApplicationRef.whenStable() 또는 fixture.whenStable()을 사용하여 쿼리 완료를 기다릴 수 있습니다. 이는 Zone.js 및 Zoneless 설정 모두에서 작동합니다.

이 통합에는 Angular 19 이상이 필요합니다. 이전 버전의 Angular는 PendingTasks를 지원하지 않습니다.

TestBed 설정

각 spec마다 새로운 QueryClient를 생성하고 provideTanStackQuery 또는 provideQueryClient와 함께 제공하세요. 이렇게 하면 캐시가 서로 격리되고 테스트마다 기본 옵션을 변경할 수 있습니다:

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false, // ✅ faster failure tests
},
},
})

TestBed.configureTestingModule({
providers: [provideTanStackQuery(queryClient)],
})

애플리케이션의 실제 TanStack Query 구성을 단위 테스트에서 사용하는 경우, 테스트 provider에 withDevtools가 실수로 포함되지 않았는지 확인합니다. 이로 인해 테스트가 느려질 수 있습니다. 테스트 구성과 프로덕션 구성은 분리하는 것이 가장 좋습니다.

헬퍼를 공유하는 경우 한 테스트의 데이터가 다른 테스트로 절대 유입되지 않도록 afterEach에서 queryClient.clear()를 호출하거나 새 인스턴스를 생성해야 합니다.

첫 번째 쿼리 테스트

쿼리 테스트는 일반적으로 TestBed.runInInjectionContext 내부에서 실행한 다음 안정화될 때까지 기다립니다:

const appRef = TestBed.inject(ApplicationRef)
const query = TestBed.runInInjectionContext(() =>
injectQuery(() => ({
queryKey: ['greeting'],
queryFn: () => 'Hello',
})),
)

TestBed.tick() // Trigger effect

// Application is stable when queries are idle
await appRef.whenStable()

expect(query.status()).toBe('success')
expect(query.data()).toBe('Hello')

PendingTasks를 사용하면 쿼리가 완료된 후 whenStable()이 이행됩니다. 가짜 타이머(Vitest)를 사용할 때는 안정성을 기다리기 전에 시계와 마이크로태스크 하나를 진행합니다:

await vi.advanceTimersByTimeAsync(0)
await Promise.resolve()
await appRef.whenStable()

컴포넌트 테스트하기

컴포넌트의 경우 TestBed.createComponent를 통해 부트스트랩한 다음, fixture.whenStable()을 await합니다:

const fixture = TestBed.createComponent(ExampleComponent)

await fixture.whenStable()
expect(fixture.componentInstance.query.data()).toEqual({ value: 42 })

재시도 처리

기본 백오프가 세 번 실행되므로 재시도는 실패하는 테스트를 느리게 합니다. 테스트를 빠르게 유지하려면 defaultOptions를 통해 또는 쿼리별로 retry: false(또는 특정 숫자)을 설정하세요. 쿼리가 의도적으로 재시도하는 경우 중간 횟수 대신 최종 상태를 검증하세요.

HttpClient 및 네트워크 스텁

Angular의 HttpClientTestingModule은 PendingTasks와 원활하게 연동됩니다. Query provider와 함께 등록하고 HttpTestingController를 통해 응답을 플러시하세요:

TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [provideTanStackQuery(queryClient)],
})

const httpCtrl = TestBed.inject(HttpTestingController)
const query = TestBed.runInInjectionContext(() =>
injectQuery(() => ({
queryKey: ['todos'],
queryFn: () => lastValueFrom(TestBed.inject(HttpClient).get('/api/todos')),
})),
)

const fixturePromise = TestBed.inject(ApplicationRef).whenStable()
httpCtrl.expectOne('/api/todos').flush([{ id: 1 }])
await fixturePromise

expect(query.data()).toEqual([{ id: 1 }])
httpCtrl.verify()

무한 쿼리 및 페이지네이션

무한 쿼리에도 같은 패턴을 사용합니다. fetchNextPage()를 호출하고, 시간을 모의로 설정했다면 타이머를 진행한 다음, 안정화될 때까지 기다리고 data().pages를 단언합니다.

const infinite = TestBed.runInInjectionContext(() =>
injectInfiniteQuery(() => ({
queryKey: ['pages'],
queryFn: ({ pageParam = 1 }) => fetchPage(pageParam),
getNextPageParam: (last, all) => all.length + 1,
})),
)

await appRef.whenStable()
expect(infinite.data().pages).toHaveLength(1)

await infinite.fetchNextPage()
await vi.advanceTimersByTimeAsync(0)
await appRef.whenStable()

expect(infinite.data().pages).toHaveLength(2)

뮤테이션과 낙관적 업데이트

const mutation = TestBed.runInInjectionContext(() =>
injectMutation(() => ({
mutationFn: async (input: string) => input.toUpperCase(),
})),
)

mutation.mutate('test')

// Trigger effect
TestBed.tick()

await appRef.whenStable()

expect(mutation.isSuccess()).toBe(true)
expect(mutation.data()).toBe('TEST')

빠른 체크리스트

  • 테스트마다 새로운 QueryClient를 사용합니다(이후 삭제합니다)
  • 시간 초과를 방지하도록 재시도를 비활성화하거나 제어합니다
  • 가짜 타이머를 사용할 때 whenStable() 전에 타이머와 마이크로태스크를 진행합니다
  • 네트워크 호출을 검증하려면 HttpClientTestingModule 또는 선호하는 mock을 사용합니다
  • refetch, fetchNextPage 또는 뮤테이션 후에 whenStable()을 await합니다
  • 서비스 테스트에는 TestBed.runInInjectionContext를, 컴포넌트 테스트에는 fixture.whenStable()을 사용하는 것이 좋습니다