LoginSignup
33
38

More than 5 years have passed since last update.

angular.io Guide: HttpClient

Posted at

この記事は、Angular の公式ドキュメントの HttpClient の章を意訳したものです。所々抜け落ち、翻訳モレがあるかと思いますがあしからずご了承を。
バージョン 4.3.3 のドキュメントをベースにしています。

HttpClient

ほとんどのフロントエンドアプリケーションは、HTTPプロトコルを使用してバックエンドサービスと通信します。モダンブラウザーは、HTTP リクエストを行うための2つのAPI、XMLHttpRequest インターフェースとfetch()APIをサポートしています。

訳注: fetch API
2015年の春〜夏頃にかけて各ブラウザがサポートしはじめた、新しいリソース取得のためのインターフェース。
現時点ではIE11のみ未対応。(Edgeは14以降で対応済み、iOS Safariは10.3で対応済み)

HttpClientを使用すると、@angular/common/httpは、ブラウザによって公開されるXMLHttpRequestインターフェイスの上に構築され、Angularアプリケーションで使用するためのHTTP機能を簡略化したAPIを提供します。 HttpClientを用いる他の利点としては、テストを容易に書くためのサポート、リクエストとレスポンスオブジェクトの厳密な型定義ならびにインターセプターのサポート、Observableに基づいた、APIベースによる優れたエラーハンドリング処理などがあります。

セットアップ: モジュールのインストール

HttpClientを使用する前に、それを提供するHttpClientModuleをインストールする必要があります。 これはアプリケーションモジュールで行うことができ、この作業が必要なのは最初の1回だけです。

// app.module.ts:

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

// Import HttpClientModule from @angular/common/http
import {HttpClientModule} from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    // Include it under 'imports' in your application module
    // after BrowserModule.
    HttpClientModule,
  ],
})
export class MyAppModule {}

HttpClientModuleをアプリケーションモジュールにインポートすると、コンポーネントとサービスにHttpClientをinjectできます。

JSONデータのリクエスト

最も一般的なパターンとして、アプリケーションは、JSONデータをバックエンドにリクエストすることではないでしょうか。たとえば、次の形式のJSONオブジェクトを返すアイテムをリストするAPIエンドポイント /api/items があるとします。

{
  "results": [
    "Item 1",
    "Item 2",
  ]
}

HttpClientget()メソッドは、このデータに簡単にアクセスできます。

@Component(...)
export class MyComponent implements OnInit {

  results: string[];

  // Inject HttpClient into your component or service.
  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    // Make the HTTP request:
    this.http.get('/api/items').subscribe(data => {
      // Read the result field from the JSON response.
      this.results = data['results'];
    });
  }
}

レスポンスの型チェック

上記の例では、大括弧表記を使用して結果フィールドにアクセスするため、data ['results']フィールドのアクセスが目立つようになりました。data.resultsを書き込もうとすると、TypeScriptはHTTPから戻ってくるオブジェクトにresultsプロパティがないことをレポートします。これは、HttpClientがJSONレスポンスをオブジェクトとして解析している間に、そのオブジェクトの型がわからないためです。

ただし、HttpClientにどのような型のレスポンスかを伝えることができます。これが推奨されます。これを行うには、まず正しい型インタフェースを定義します。

interface ItemsResponse {
  results: string[];
}

次に、HttpClient.getの呼び出しを行うときに、型パラメーターを渡します。

http.get<ItemsResponse>('/api/items').subscribe(data => {
  // data is now an instance of type ItemsResponse, so you can do this:
  this.results = data.results;
});

完全なレスポンスを参照する

レスポンスボディは必要なすべてのデータを返しません。特定の状況下であることを伝えるためにサーバーが特殊なヘッダーやステータスコードを返す場合、これらのチェックが必要なケースもあるでしょう。HttpClientにはobserveオプションを使うことで、指定したレスポンスボディだけでなく、完全なレスポンスを求めるようにできます。

http
  .get<MyJsonData>('/data.json', {observe: 'response'})
  .subscribe(resp => {
    // Here, resp is of type HttpResponse<MyJsonData>.
    // You can inspect its headers:
    console.log(resp.headers.get('X-Custom-Header'));
    // And access the body directly, which is typed as MyJsonData as requested.
    console.log(resp.body.someField);
  });

ご覧のように、返ってきたオブジェクトには、正しい型のbodyプロパティがあります。

エラーハンドリング

サーバー上でリクエストが失敗した場合、またはネットワーク接続が不安定なためにサーバーにリクエストを送信できない場合はどうなるでしょうか。HttpClientは、正常なレスポンスではなくエラーを返します。

これを処理するには、エラーハンドラーを.subscribe()callbackに追加します。

http
  .get<ItemsResponse>('/api/items')
  .subscribe(
    // Successful responses call the first callback.
    data => {...},
    // Errors will call this callback instead:
    err => {
      console.log('Something went wrong!');
    }
  });

エラーの詳細を取得する

エラーが発生したことを検出することも重要ですが、実際にどのようなエラーが発生したのかを知ることがより効果的です。 上記のコールバックのerrパラメータはHttpErrorResponse型で、何がうまくいかなかったのかがわかる有益な情報が含まれています。

発生する可能性のあるエラーには2種類あります。バックエンド側が失敗したレスポンスコード(404, 500など)を返すと、エラーとして返されます。また、RxJSオペレータで例外がスローされたり、ネットワークエラーによってリクエストが正常に完了しなかったりするなど、何らかのクライアント側で問題が発生した場合は、実際のエラーがスローされます。

いずれの場合も、HttpErrorResponseを見れば何が起きたか把握できます。

http
  .get<ItemsResponse>('/api/items')
  .subscribe(
    data => {...},
    (err: HttpErrorResponse) => {
      if (err.error instanceof Error) {
        // A client-side or network error occurred. Handle it accordingly.
        console.log('An error occurred:', err.error.message);
      } else {
        // The backend returned an unsuccessful response code.
        // The response body may contain clues as to what went wrong,
        console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
      }
    }
  });

.retry()

エラーに対処する1つの方法は、単にリクエストをリトライすることです。この戦略は、エラーが一時的であり、繰り返される可能性が低い場合に役立ちます。

RxJSには、Observableに自動的に再登録し、エラーが発生したときにリクエストを再発行する.retry()という便利な演算子があります。

まずはじめに、インポートします。

import 'rxjs/add/operator/retry';

次に、このようなHTTP Observablesで使用することができます:

http
  .get<ItemsResponse>('/api/items')
  // Retry this request up to 3 times.
  .retry(3)
  // Any errors after the 3rd retry will fall through to the app.
  .subscribe(...);

非JSONデータをリクエストする

すべてのAPIがJSONデータを返すわけではありません。サーバー上のテキストファイルを読み込みたいこともあるでしょう。その場合、あなたはHttpClientにテキストレスポンスを期待する必要があります:

http
  .get('/textfile.txt', {responseType: 'text'})
  // The Observable returned by get() is of type Observable<string>
  // because a text response was specified. There's no need to pass
  // a <string> type parameter to get().
  .subscribe(data => console.log(data));

データをサーバーに送信する

HttpClientは、サーバーからデータを取得するだけでなく、さまざまな形でサーバーにデータを送信するという、変更リクエストもサポートしています。

1つの一般的な操作は、データをサーバーにPOSTすることです。例えばフォームをsubmitするときなどです。 POSTリクエストを送信するコードは、GETのコードと非常によく似ています。

POST リクエストの生成

const body = {name: 'Brad'};

http
  .post('/api/developers/add', body)
  // See below - subscribe() is still necessary when using post().
  .subscribe(...);

subscribe()メソッドに注意してください。HttpClientから返されるすべてのObservablesは cold であり、リクエストを行うための blueprint です。 subscribe()を呼び出すまでは何も起こりません。そのような呼び出しのたびに別のリクエストが行われます。
例えば、下記のコードは同じデータを持つPOSTリクエストを2回送信します。

const req = http.post('/api/items/add', body);
// 0 requests made - .subscribe() not called.
req.subscribe();
// 1 request made.
req.subscribe();
// 2 requests made. 

リクエストの他の部分の設定

URLとリクエストボディのほかにも、設定したい送信リクエストの他の側面もあります。 これらのすべてはオプションオブジェクトを介して利用できます。オプションオブジェクトはリクエストに渡します。

ヘッダー

一般的なタスクの1つとして、リクエストに Authorization ヘッダーを追加することです。ここでそれを行う方法は次のとおりです。

http
  .post('/api/items/add', body, {
    headers: new HttpHeaders().set('Authorization', 'my-auth-token'),
  })
  .subscribe();

HttpHeadersクラスはimmutableなので、すべてのset()は新しいインスタンスを返し、変更を適用します。

URL パラメーター

URLパラメータの追加も、上記と同じ方法で動作します。id パラメータを 3 に設定してリクエストを送信するには、次のようにします。

http
  .post('/api/items/add', body, {
    params: new HttpParams().set('id', '3'),
  })
  .subscribe();

このようにして、POSTリクエストをURL /api/items/add?id=3 に送信します。

高度な使い方

上記のセクションでは、@angular/common/http の基本的なHTTP機能を使用する方法について詳しく説明しましたが、リクエストを作成してデータを戻すだけではなく、さらに多くのことを行いたい場合について説明していきます。

すべてのリクエスト/レスポンスのインターセプト

@angular/common/httpの主な特徴は、アプリケーションとバックエンドの間にあるインターセプタを宣言するインターセプトです。アプリケーションがリクエストを行うと、インターセプタはそれをサーバーに送信する前に変換し、インターセプタはアプリケーションが見る前にレスポンスを変換します。これは、認証からロギングまでのすべてに役立ちます。

インターセプタを書く

インターセプタを実装するには、1つのintercept()メソッドを持つHttpInterceptorを実装するクラスを宣言します。このシンプルなインターセプタは、リクエストを変更せずにそのまま転送するだけです。

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class NoopInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

interceptは、リクエストをObservableに変換して、最終的にレスポンスを返すメソッドです。各インターセプタは、単独でリクエストを処理する責任を持つことを意味しています。

しかし、ほとんどの場合、インターセプタはリクエストに若干の変更を加え、それをチェーンの他の部分に転送します。それは次のパラメータが来るところです。nextHttpHandlerです。これはインターセプトに似ているリクエストを、レスポンスのObservableに変換します。 インターセプタでは、nextは常にチェーン内の次のインターセプタを表します(存在する場合)。インターセプタがない場合は最終的なバックエンドを表します。したがって、ほとんどのinterceptorは、彼らが加工したリクエストでnextを呼び出すことで終わります。

私たちの何もしないハンドラーは、単に元のリクエストに対してnext.handleを呼び出し、それをまったく変更することなく転送しているだけです。

このパターンは、Express.jsなどのミドルウェアフレームワークのパターンに似ています。

インターセプタを提供する

上記のNoopInterceptorを宣言しても、即座にアプリ側で使われるわけではありません。次のように、インターセプターとしてアプリケーションモジュールを提供することによって、アプリケーションモジュールでワイヤリングする必要があります。

import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';

@NgModule({
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: NoopInterceptor,
    multi: true,
  }],
})
export class AppModule {}

multi:trueオプションに注意してください。これは必須であり、AnglarにHTTP_INTERCEPTORSは単一の値ではなく値の配列であることを伝えています。

イベント

また、InterceptHttpHandler.handleによって返されるObservableObservable<HttpResponse<any>>ではなく、Observable<HttpEvent<any>ではないことに気づいたかもしれません。これは、インターセプタがHttpClientインターフェイスよりも低いレベルで動作するためです。1回のリクエストで、アップロードおよびダウンロードの進行状況イベントを含む複数のイベントを生成できます。HttpResponseクラスは、実際にはHttpEventType.HttpResponseEvent型のイベント自体です。

インターセプタは、理解できない、または変更の予定がないイベントも含めて、すべてのイベントを通過する必要があります。処理する予定のないイベントを除外してはいけません。多くのインターセプタは発信リクエストのみに関心があり、変更せずに次のイベントストリームを返すだけです。

順番 (Ordering)

アプリケーションで複数のインターセプタを提供する場合、Angularはそれらを指定した順序で適用します。

不変性 (Immunitability)

インターセプタは、発信リクエストと着信レスポンスを調べて変更するために存在します。しかし、HttpRequestクラスとHttpResponseクラスがほとんどimmutableであることを知ると驚くかもしれません。

これは、アプリがリクエストを再試行する可能性があるため、インターセプタチェーンが個別のリクエストを複数回処理する可能性があるためです。リクエストが変更可能であった場合、再試行されたリクエストは元のリクエストとは異なります。不変性は、インターセプタが各試行に対して同じリクエストを見ることを保証します。

インターセプタを書くときにタイプセーフがあなたを守ることができないケースが1つあります。リクエストボディです。インターセプタ内のリクエスト本体を変更することはinvalidですが、これは型システムによってチェックされません。

リクエストボディを変更する必要がある場合は、リクエストボディをコピーして、コピーを変更してから、clone()を使用してリクエストをコピーし、新しいボディをセットする必要があります。

リクエストはimmutableなので、直接変更することはできません。これらを変更するには、clone()を使用します。

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  // This is a duplicate. It is exactly the same as the original.
  const dupReq = req.clone();

  // Change the URL and replace 'http://' with 'https://'
  const secureReq = req.clone({url: req.url.replace('http://', 'https://')});
}

ご覧のように、clone() によって受け入れられたハッシュは、他のものをコピーしている間にリクエストの特定のプロパティを変更することを可能にします。

新しいheaderをセットする

インターセプタの一般的な使用法は、発信するリクエストにデフォルトのヘッダーを設定することです。たとえば、認証トークンを提供できるAuthServiceがあるとして、それをすべての発信リクエストに付加するインターセプタを作成する方法は次のとおりです。

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the auth header from the service.
    const authHeader = this.auth.getAuthorizationHeader();
    // Clone the request to add the new header.
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    // Pass on the cloned request instead of the original request.
    return next.handle(authReq);
  }
}

新しいヘッダーを設定するリクエストを複製する方法は非常に一般的であり、実際にはそのためのショートカットがあります。

const authReq = req.clone({setHeaders: {Authorization: authHeader}});

ヘッダーを変更するインターセプターは、次のようなさまざまな操作に使用できます。

  • 認証/承認 (Authentication/authorization)
  • キャッシュ動作(例えばIf-Modified-Since
  • XSRF プロテクション

ロギング

インターセプタはリクエストとレスポンスを一緒に処理できるため、ログリクエストやタイムリクエストなどを行うことができます。console.logを使用して各リクエストの所要時間を示すこのインターセプタを考えてみましょう。

import 'rxjs/add/operator/do';

export class TimingInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const started = Date.now();
    return next
      .handle(req)
      .do(event => {
        if (event instanceof HttpResponse) {
          const elapsed = Date.now() - started;
          console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
        }
      });
  }
}

RxJSのdo()演算子に注目してください。ストリームの値に影響を与えずにObservableに副作用を追加できます。ここでは、HttpResponseイベントを検出し、リクエストが取得された時刻を記録します。

キャッシュ

インターセプタを使用してキャッシング機構を実装することもできます。この例では、シンプルなインターフェイスでHTTPキャッシュを作成したと仮定します。

abstract class HttpCache {
  /**
   * Returns a cached response, if any, or null if not present.
   */
  abstract get(req: HttpRequest<any>): HttpResponse<any>|null;

  /**
   * Adds or updates the response in the cache.
   */
  abstract put(req: HttpRequest<any>, resp: HttpResponse<any>): void;
}

インターセプタは、このキャッシュを発信リクエストに適用できます。

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
  constructor(private cache: HttpCache) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Before doing anything, it's important to only cache GET requests.
    // Skip this interceptor if the request method isn't GET.
    if (req.method !== 'GET') {
      return next.handle(req);
    }

    // First, check the cache to see if this request exists.
    const cachedResponse = this.cache.get(req);
    if (cachedResponse) {
      // A cached response exists. Serve it instead of forwarding
      // the request to the next handler.
      return Observable.of(cachedResponse);
    }

    // No cached response exists. Go to the network, and cache
    // the response when it arrives.
    return next.handle(req).do(event => {
      // Remember, there may be other events besides just the response.
      if (event instanceof HttpResponse) {
        // Update the cache.
        this.cache.put(req, event);
      }
    });
  }
}

明らかに、この例ではリクエストの一致、キャッシュの無効化などについて説明していますが、リクエストを変換するだけでなく、インターセプタが多くの力を持つことは容易にわかります。必要に応じて、それらを使用してリクエストフローを完全に引き継ぐことができます。

柔軟性を実際に実証するために、リクエストがキャッシュに存在する場合(キャッシュされたレスポンスがはじめからある場合)、更新されたネットワークレスポンスが後で存在する場合は、2つのレスポンスイベントを返すように変更できます。

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  // Still skip non-GET requests.
  if (req.method !== 'GET') {
    return next.handle(req);
  }

  // This will be an Observable of the cached value if there is one,
  // or an empty Observable otherwise. It starts out empty.
  let maybeCachedResponse: Observable<HttpEvent<any>> = Observable.empty();

  // Check the cache.
  const cachedResponse = this.cache.get(req);
  if (cachedResponse) {
    maybeCachedResponse = Observable.of(cachedResponse);
  }

  // Create an Observable (but don't subscribe) that represents making
  // the network request and caching the value.
  const networkResponse = next.handle(req).do(event => {
    // Just like before, check for the HttpResponse event and cache it.
    if (event instanceof HttpResponse) {
      this.cache.put(req, event);
    }
  });

  // Now, combine the two and send the cached response first (if there is
  // one), and the network response second.
  return Observable.concat(maybeCachedResponse, networkResponse);
}

現在、http.get(url)を行っている人は、そのURLが以前にキャッシュされていれば、2つのレスポンスを受け取ります。

進捗イベントを監視する

アプリケーションが大量のデータを転送する必要があり、転送に時間がかかることがあります。このような転送の進捗状況に関するフィードバックを提供することは、UXとして優れています。たとえば、ファイルをアップロードすると、@angular/common/httpがこれをサポートします。

進捗イベントを有効にしてリクエストを行うには、まず特別なreportProgressオプションを設定してHttpRequestのインスタンスを作成します。

const req = new HttpRequest('POST', '/upload/file', file, {
  reportProgress: true,
});

このオプションを使用すると、進行状況イベントを追跡できます。各イベントでUIを実際に更新する予定がある場合は、進捗イベントごとに変更の検出が行われることを覚えておいてください。

次にHttpClientrequest()メソッドを通じてリクエストを行います。
結果はインターセプタの場合と同様、イベントのObservableとなります。

http.request(req).subscribe(event => {
  // Via this API, you get access to the raw event stream.
  // Look for upload progress events.
  if (event.type === HttpEventType.UploadProgress) {
    // This is an upload progress event. Compute and show the % done:
    const percentDone = Math.round(100 * event.loaded / event.total);
    console.log(`File is ${percentDone}% uploaded.`);
  } else if (event instanceof HttpResponse) {
    console.log('File is completely uploaded!');
  }
});

セキュリティ: XSRF プロテクション

クロスサイトリクエストフォージェリ(XSRF)とは、攻撃者が認証されたユーザーを知らずにあなたのWebサイト上のアクションを実行させる攻撃手法です。HttpClientは、XSRF攻撃を防止するために使用される共通のメカニズムをサポートしています。HTTPリクエストを実行するとき、インターセプタはデフォルトでXSRF-TOKENによってCookieからトークンを読み取り、それをHTTPヘッダーX-XSRF-TOKENとしてセットします。ドメイン上で動作するコードだけがCookieを読み取ることができるため、バックエンドはHTTPリクエストが攻撃者ではなくクライアントアプリケーションからのものであることを確認できます。

デフォルトでは、インターセプタはすべての変更リクエスト(POSTなど)でこのCookieを相対URLに送信しますが、GET/HEADリクエストや絶対URLを持つリクエストでは送信しません。

これを利用するには、ページ読み込みまたは最初のGETリクエストのいずれかでXSRF-TOKENというJavaScriptで読み取れるセッションクッキーにトークンを設定する必要があります。その後のリクエストでは、サーバーはCookieがX-XSRF-TOKENHTTPヘッダーと一致することを確認できるため、ドメインで実行されているコードのみがリクエストを送信できたことを確認します。トークンはユーザーごとに一意でなければならず、サーバーによって検証可能でなければなりません。これにより、クライアントは独自のトークンを作成することができなくなります。セキュリティを強化するために、トークンをサイトの認証Cookieのダイジェストに設定します。

複数のAngularアプリケーションが同じドメインまたはサブドメインを共有する環境でコンフリクトを防ぐために、各アプリケーションごとにユニークなCookie名を付けるようにしてください。

  • HttpClient のサポートは、XSRF保護スキームのクライアントの半分にすぎないことに注意してください。
  • あなたのバックエンドサービスは、ページのクッキーを設定し、すべての適格なリクエストにヘッダーが存在することを確認するように設定する必要があります。そうでない場合、Angularのデフォルトの保護は無効になります。

カスタム cookie/header 名を設定する

バックエンドサービスがXSRF トークンのCookieまたはヘッダーに異なる名前を使用したい場合は、HttpClientXsrfModule.withConfig() を使用してデフォルトの設定を上書きできます。

imports: [
  HttpClientModule,
  HttpClientXsrfModule.withConfig({
    cookieName: 'My-Xsrf-Cookie',
    headerName: 'My-Xsrf-Header',
  }),
]

HTTP リクエストのテスト

外部依存関係のテストと同様に、HTTPバックエンドは良いテストの練習の一部としてモックされる必要があります。@angular/common/httpは、そのようなモッキングを簡単に設定するテストライブラリ@angular/common/http/testingを提供しています。

モックの考え方

AngularのHTTPテストライブラリは、アプリケーションがコードを実行してリクエストを最初に行うテストのパターン用に設計されています。その後、テストでは特定のリクエストがあるかどうか、それらのリクエストに対してアサーションを実行し、最後に期待されるリクエストを「フラッシング」することによってレスポンスを提供します。これにより、より多くの新しいリクエストがトリガーされる可能性があります。アプリが予期せぬリクエストをしていないことを確認します。

セットアップ

HttpClientを通じて行われたリクエストのテストを開始するには、HttpClientTestingModuleをインポートしてTestBedの設定に追加します。

import {HttpClientTestingModule} from '@angular/common/http/testing';

beforeEach(() => {
  TestBed.configureTestingModule({
    ...,
    imports: [
      HttpClientTestingModule,
    ],
  })
});

これでおしまいです。テストの過程で行われたリクエストは、通常のバックエンドの代わりにテストバックエンドに当たるでしょう。

リクエストの期待と回答

モックをモジュール経由でインストールすると、GETリクエストが発生することを期待するテストを作成し、モックレスポンスを提供することができます。次の例では、HttpClientをテストにinjectし、HttpTestingControllerというクラスをinjectします。

it('expects a GET request', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
  // Make an HTTP GET request, and expect that it return an object
  // of the form {name: 'Test Data'}.
  http
    .get('/data')
    .subscribe(data => expect(data['name']).toEqual('Test Data'));

  // At this point, the request is pending, and no response has been
  // sent. The next step is to expect that the request happened.
  const req = httpMock.expectOne('/data');

  // If no request with that URL was made, or if multiple requests match,
  // expectOne() would throw. However this test makes only one request to
  // this URL, so it will match and return a mock request. The mock request
  // can be used to deliver a response or make assertions against the
  // request. In this case, the test asserts that the request is a GET.
  expect(req.request.method).toEqual('GET');

  // Next, fulfill the request by transmitting a response.
  req.flush({name: 'Test Data'});

  // Finally, assert that there are no outstanding requests.
  httpMock.verify();
}));

最後のステップは、リクエストがまだ未解決のままであることを確認することですが、afterEach()ステップに移動するのに十分です。

afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
  httpMock.verify();
}));

カスタムリクエストの期待

URLによる照合では不十分な場合は、独自の照合機能を実装することができます。たとえば、Authorization ヘッダーを持つ発信リクエストを検索できます。

const req = httpMock.expectOne((req) => req.headers.has('Authorization'));

上記のテストのURLで expectOne() と同様に、0または2以上のリクエストがこの期待値に一致するとスローされます。

複数のリクエストの処理

テストで重複したリクエストにレスポンスする必要がある場合は、expectOne() の代わりに match() APIを使用します。これは同じ引数を取りますが、一致するリクエストの配列を返します。返されたこれらのリクエストは、今後の照合から削除され、確認してフラッシュする責任があります。

// Expect that 5 pings have been made and flush them.
const reqs = httpMock.match('/ping');
expect(reqs.length).toBe(5);
reqs.forEach(req => req.flush());
33
38
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
38