Angular9 测试 HTTP 请求

2020-07-06 16:08 更新

如同所有的外部依赖一样,你必须把 HTTP 后端也 Mock 掉,以便你的测试可以模拟这种与后端的互动。 @angular/common/http/testing 库能让这种 Mock 工作变得直截了当。

Angular 的 HTTP 测试库是专为其中的测试模式而设计的。在这种模式下,会首先在应用中执行代码并发起请求。 然后,这个测试会期待发起或未发起过某个请求,并针对这些请求进行断言, 最终对每个所预期的请求进行刷新(flush)来对这些请求提供响应。

最终,测试可能会验证这个应用不曾发起过非预期的请求。

本章所讲的这些测试位于 "src/testing/http-client.spec.ts" 中。 在 "src/app/heroes/heroes.service.spec.ts" 中还有一些测试,用于测试那些调用了 "HttpClient" 的数据服务。

搭建测试环境

要开始测试那些通过 HttpClient 发起的请求,就要导入 HttpClientTestingModule 模块,并把它加到你的 TestBed 设置里去,代码如下:

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 中,并继续设置被测服务。

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 ///
});

现在,在测试中发起的这些请求会发给这些测试用的后端(testing backend),而不是标准的后端。

这种设置还会调用 TestBed.inject(),来获取注入的 HttpClient 服务和模拟对象的控制器 HttpTestingController,以便在测试期间引用它们。

期待并回复请求

现在,你就可以编写测试,等待 GET 请求并给出模拟响应。

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();
});

最后一步,验证没有发起过预期之外的请求,足够通用,因此你可以把它移到 afterEach() 中:

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

  1. 自定义对请求的预期

如果仅根据 URL 匹配还不够,你还可以自行实现匹配函数。 比如,你可以验证外发的请求是否带有某个认证头:

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

像前面的 expectOne() 测试一样,如果零或两个以上的请求满足了这个断言,它就会抛出异常。

  1. 处理一个以上的请求

如果你需要在测试中对重复的请求进行响应,可以使用 match() API 来代替 expectOne(),它的参数不变,但会返回一个与这些请求相匹配的数组。一旦返回,这些请求就会从将来要匹配的列表中移除,你要自己验证和刷新(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);

测试对错误的预期

你还要测试应用对于 HTTP 请求失败时的防护。

调用 request.flush() 并传入一个错误信息,如下所示:

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 来调用 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);
});
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号