リクエストのテスト
外部へ依存する場合、テストでリモートサーバーとのやり取りをシミュレートできるように、HTTPバックエンドをモックする必要があります。@angular/common/http/testing
ライブラリは、アプリケーションによって行われたリクエストをキャプチャし、それらについてアサーションを行い、バックエンドの動作をエミュレートするためにレスポンスをモックするためのツールを提供します。
テストライブラリは、アプリケーションがコードを実行して最初にリクエストを行うというパターン用に設計されています。その後、テストは特定のリクエストが行われたか、行われなかったかを期待し、それらのリクエストに対してアサーションを行い、最後に各期待されるリクエストにレスポンスを提供することによってそれらを「フラッシュ」します。
最後に、テストはアプリケーションが予期しないリクエストを行わなかったことを検証できます。
テストの設定
HttpClient
の使用のテストを開始するには、TestBed
を構成してテストの設定に provideHttpClient()
と provideHttpClientTesting()
を含めます。これにより、HttpClient
が実際のネットワークではなくテストバックエンドを使用するように構成されます。また、HttpTestingController
も提供され、これを使用してテストバックエンドとやり取ります。そして、どのリクエストが行われたかについての期待を設定し、それらのリクエストに対するレスポンスを流します。HttpTestingController
は、構成されたら TestBed
から注入できます。
provideHttpClientTesting()
は provideHttpCient()
の一部を上書きするため、provideHttpClient()
を provideHttpClientTesting()
の前に提供することに留意してください。これを逆に行うと、テストが壊れてしまう可能性があります。
TestBed.configureTestingModule({ providers: [ // ...その他のテストプロバイダー provideHttpClient(), provideHttpClientTesting(), ],});const httpTesting = TestBed.inject(HttpTestingController);
これで、テストでリクエストを行うと、通常のバックエンドではなくテストバックエンドにヒットするようになります。httpTesting
を使用して、それらのリクエストについてアサーションを行うことができます。
リクエストの期待と応答
たとえば、GETリクエストが発生することを期待し、モックレスポンスを提供するテストを作成できます。
TestBed.configureTestingModule({ providers: [ ConfigService, provideHttpClient(), provideHttpClientTesting(), ],});const httpTesting = TestBed.inject(HttpTestingController);// `ConfigService` をロードして現在の構成を要求します。const service = TestBed.inject(ConfigService);const config$ = this.configService.getConfig<Config>();// `firstValueFrom` は `Observable` を購読し、HTTP リクエストを行い、// レスポンスの `Promise` を作成します。const configPromise = firstValueFrom(config$);// この時点で、リクエストは保留中であり、// `HttpTestingController` を介して作成されたことをアサートできます。const req = httpTesting.expectOne('/api/config', '構成をロードするリクエスト');// 必要に応じて、リクエストのさまざまなプロパティをアサートできます。expect(req.request.method).toBe('GET');// リクエストをフラッシュすると、リクエストが完了し、結果が配信されます。req.flush(DEFAULT_CONFIG);// その後、`ConfigService` によってレスポンスが正常に配信されたことをアサートできます。expect(await configPromise).toEqual(DEFAULT_CONFIG);// 最後に、他のリクエストが行われていないことをアサートできます。httpTesting.verify();
Note: expectOne
は、テストが指定された基準に一致するリクエストを2つ以上行った場合に失敗します。
req.method
についてアサートする代わりに、expectOne
の拡張形式を使用してリクエストメソッドも一致させることができます。
const req = httpTesting.expectOne({ method: 'GET', url: '/api/config',}, '構成をロードするリクエスト');
HELPFUL: 期待APIは、クエリパラメータを含むリクエストの完全なURLと一致します。
最後のステップである、保留中のリクエストがないことを検証する操作は十分に一般的なので、afterEach()
ステップに移動できます。
afterEach(() => { // テストが追加の HTTP リクエストを行わないことを確認します。 TestBed.inject(HttpTestingController).verify();});
複数のリクエストを一度に処理する
テストで重複するリクエストに応答する必要がある場合は、expectOne()
ではなく match()
APIを使用します。これは同じ引数を受け取りますが、一致するリクエストの配列を返します。返された後、これらのリクエストは今後のマッチングから削除され、それらをフラッシュして検証する責任はあなたにあります。
const allGetRequests = httpTesting.match({method: 'GET'});for (const req of allGetRequests) { // 各リクエストへの応答処理。}
高度なマッチング
すべてのマッチング関数は、カスタムマッチングロジックの述語関数を受け取ります。
// リクエストボディを持つリクエストを 1 つ探します。const requestsWithBody = httpTesting.expectOne(req => req.body !== null);
expectNone
関数は、指定された基準に一致するリクエストがないことをアサートします。
// 突然変異リクエストが発行されていないことをアサートします。httpTesting.expectNone(req => req.method !== 'GET');
エラー処理のテスト
HTTPリクエストが失敗した場合のアプリケーションのレスポンスをテストする必要があります。
バックエンドエラー
バックエンドエラー(サーバーが成功ではないステータスコードを返す場合)の処理をテストするには、リクエストが失敗した場合にバックエンドが返すものとエミュレートしたエラーレスポンスを使用して、リクエストをフラッシュします。
const req = httpTesting.expectOne('/api/config');req.flush('Failed!', {status: 500, statusText: 'Internal Server Error'});// アプリケーションがバックエンドエラーを正常に処理したことをアサートします。
ネットワークエラー
リクエストは、ネットワークエラーが原因で失敗することもあり、ProgressEvent
エラーとして現れます。これらは、error()
メソッドを使用して配信できます。
const req = httpTesting.expectOne('/api/config');req.error(new ProgressEvent('network error!'));// アプリケーションがネットワークエラーを正常に処理したことをアサートします。
Testing an Interceptor
You should test that your interceptors work under the desired circumstances.
For example, an application may be required to add an authentication token generated by a service to each outgoing request. This behavior can be enforced with the use of an interceptor:
export function authInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> { const authService = inject(AuthService); const clonedRequest = request.clone({ headers: request.headers.append('X-Authentication-Token', authService.getAuthToken()), }); return next(clonedRequest);}
The TestBed
configuration for this interceptor should rely on the withInterceptors
feature.
TestBed.configureTestingModule({ providers: [ AuthService, // Testing one interceptor at a time is recommended. provideHttpClient(withInterceptors([authInterceptor])), provideHttpClientTesting(), ],});
The HttpTestingController
can retrieve the request instance which can then be inspected to ensure that the request was modified.
const service = TestBed.inject(AuthService);const req = httpTesting.expectOne('/api/config');expect(req.request.headers.get('X-Authentication-Token')).toEqual(service.getAuthToken());
A similar interceptor could be implemented with class based interceptors:
@Injectable()export class AuthInterceptor implements HttpInterceptor { private authService = inject(AuthService); intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { const clonedRequest = request.clone({ headers: request.headers.append('X-Authentication-Token', this.authService.getAuthToken()), }); return next.handle(clonedRequest); }}
In order to test it, the TestBed
configuration should instead be:
TestBed.configureTestingModule({ providers: [ AuthService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), // We rely on the HTTP_INTERCEPTORS token to register the AuthInterceptor as an HttpInterceptor { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, ],});