Angular9 測(cè)試 HTTP 請(qǐng)求

2020-07-06 16:08 更新

如同所有的外部依賴(lài)一樣,你必須把 HTTP 后端也 Mock 掉,以便你的測(cè)試可以模擬這種與后端的互動(dòng)。 @angular/common/http/testing 庫(kù)能讓這種 Mock 工作變得直截了當(dāng)。

Angular 的 HTTP 測(cè)試庫(kù)是專(zhuān)為其中的測(cè)試模式而設(shè)計(jì)的。在這種模式下,會(huì)首先在應(yīng)用中執(zhí)行代碼并發(fā)起請(qǐng)求。 然后,這個(gè)測(cè)試會(huì)期待發(fā)起或未發(fā)起過(guò)某個(gè)請(qǐng)求,并針對(duì)這些請(qǐng)求進(jìn)行斷言, 最終對(duì)每個(gè)所預(yù)期的請(qǐng)求進(jìn)行刷新(flush)來(lái)對(duì)這些請(qǐng)求提供響應(yīng)。

最終,測(cè)試可能會(huì)驗(yàn)證這個(gè)應(yīng)用不曾發(fā)起過(guò)非預(yù)期的請(qǐng)求。

本章所講的這些測(cè)試位于 "src/testing/http-client.spec.ts" 中。 在 "src/app/heroes/heroes.service.spec.ts" 中還有一些測(cè)試,用于測(cè)試那些調(diào)用了 "HttpClient" 的數(shù)據(jù)服務(wù)。

搭建測(cè)試環(huán)境

要開(kāi)始測(cè)試那些通過(guò) HttpClient 發(fā)起的請(qǐng)求,就要導(dǎo)入 HttpClientTestingModule 模塊,并把它加到你的 TestBed 設(shè)置里去,代碼如下:

Path:"app/testing/http-client.spec.ts (imports)" 。

// Http testing module and mocking controller
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';


// Other imports
import { TestBed } from '@angular/core/testing';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

然后把 HTTPClientTestingModule 添加到 TestBed 中,并繼續(xù)設(shè)置被測(cè)服務(wù)。

Path:"app/testing/http-client.spec.ts(setup)" 。

describe('HttpClient testing', () => {
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;


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


    // Inject the http service and test controller for each test
    httpClient = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
  });
  /// Tests begin ///
});

現(xiàn)在,在測(cè)試中發(fā)起的這些請(qǐng)求會(huì)發(fā)給這些測(cè)試用的后端(testing backend),而不是標(biāo)準(zhǔn)的后端。

這種設(shè)置還會(huì)調(diào)用 TestBed.inject(),來(lái)獲取注入的 HttpClient 服務(wù)和模擬對(duì)象的控制器 HttpTestingController,以便在測(cè)試期間引用它們。

期待并回復(fù)請(qǐng)求

現(xiàn)在,你就可以編寫(xiě)測(cè)試,等待 GET 請(qǐng)求并給出模擬響應(yīng)。

Path:"app/testing/http-client.spec.ts(httpClient.get)" 。

it('can test HttpClient.get', () => {
  const testData: Data = {name: 'Test Data'};


  // Make an HTTP GET request
  httpClient.get<Data>(testUrl)
    .subscribe(data =>
      // When observable resolves, result should match test data
      expect(data).toEqual(testData)
    );


  // The following `expectOne()` will match the request's URL.
  // If no requests or multiple requests matched that URL
  // `expectOne()` would throw.
  const req = httpTestingController.expectOne('/data');


  // Assert that the request is a GET.
  expect(req.request.method).toEqual('GET');


  // Respond with mock data, causing Observable to resolve.
  // Subscribe callback asserts that correct data was returned.
  req.flush(testData);


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

最后一步,驗(yàn)證沒(méi)有發(fā)起過(guò)預(yù)期之外的請(qǐng)求,足夠通用,因此你可以把它移到 afterEach() 中:

afterEach(() => {
  // After every test, assert that there are no more pending requests.
  httpTestingController.verify();
});

  1. 自定義對(duì)請(qǐng)求的預(yù)期

如果僅根據(jù) URL 匹配還不夠,你還可以自行實(shí)現(xiàn)匹配函數(shù)。 比如,你可以驗(yàn)證外發(fā)的請(qǐng)求是否帶有某個(gè)認(rèn)證頭:

    // Expect one request with an authorization header
    const req = httpTestingController.expectOne(
      req => req.headers.has('Authorization')
    );

像前面的 expectOne() 測(cè)試一樣,如果零或兩個(gè)以上的請(qǐng)求滿(mǎn)足了這個(gè)斷言,它就會(huì)拋出異常。

  1. 處理一個(gè)以上的請(qǐng)求

如果你需要在測(cè)試中對(duì)重復(fù)的請(qǐng)求進(jìn)行響應(yīng),可以使用 match() API 來(lái)代替 expectOne(),它的參數(shù)不變,但會(huì)返回一個(gè)與這些請(qǐng)求相匹配的數(shù)組。一旦返回,這些請(qǐng)求就會(huì)從將來(lái)要匹配的列表中移除,你要自己驗(yàn)證和刷新(flush)它。

// get all pending requests that match the given URL
const requests = httpTestingController.match(testUrl);
expect(requests.length).toEqual(3);


// Respond to each request with different results
requests[0].flush([]);
requests[1].flush([testData[0]]);
requests[2].flush(testData);

測(cè)試對(duì)錯(cuò)誤的預(yù)期

你還要測(cè)試應(yīng)用對(duì)于 HTTP 請(qǐng)求失敗時(shí)的防護(hù)。

調(diào)用 request.flush() 并傳入一個(gè)錯(cuò)誤信息,如下所示:

it('can test for 404 error', () => {
  const emsg = 'deliberate 404 error';


  httpClient.get<Data[]>(testUrl).subscribe(
    data => fail('should have failed with the 404 error'),
    (error: HttpErrorResponse) => {
      expect(error.status).toEqual(404, 'status');
      expect(error.error).toEqual(emsg, 'message');
    }
  );


  const req = httpTestingController.expectOne(testUrl);


  // Respond with mock error
  req.flush(emsg, { status: 404, statusText: 'Not Found' });
});

另外,你還可以使用 ErrorEvent 來(lái)調(diào)用 request.error().

it('can test for network error', () => {
  const emsg = 'simulated network error';


  httpClient.get<Data[]>(testUrl).subscribe(
    data => fail('should have failed with the network error'),
    (error: HttpErrorResponse) => {
      expect(error.error.message).toEqual(emsg, 'message');
    }
  );


  const req = httpTestingController.expectOne(testUrl);


  // Create mock ErrorEvent, raised when something goes wrong at the network level.
  // Connection timeout, DNS error, offline, etc
  const mockError = new ErrorEvent('Network error', {
    message: emsg,
  });


  // Respond with mock error
  req.error(mockError);
});
以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)