Angular9 测试 HTTP 请求
如同所有的外部依赖一样,你必须把 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();
});
- 自定义对请求的预期
如果仅根据 URL 匹配还不够,你还可以自行实现匹配函数。 比如,你可以验证外发的请求是否带有某个认证头:
// Expect one request with an authorization header
const req = httpTestingController.expectOne(
req => req.headers.has('Authorization')
);
像前面的 expectOne()
测试一样,如果零或两个以上的请求满足了这个断言,它就会抛出异常。
- 处理一个以上的请求
如果你需要在测试中对重复的请求进行响应,可以使用 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);
});
更多建议: