-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
backend.ts
155 lines (143 loc) Β· 5.44 KB
/
backend.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {HttpBackend, HttpEvent, HttpEventType, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable, Observer} from 'rxjs';
import {HttpTestingController, RequestMatch} from './api';
import {TestRequest} from './request';
/**
* A testing backend for `HttpClient` which both acts as an `HttpBackend`
* and as the `HttpTestingController`.
*
* `HttpClientTestingBackend` works by keeping a list of all open requests.
* As requests come in, they're added to the list. Users can assert that specific
* requests were made and then flush them. In the end, a verify() method asserts
* that no unexpected requests were made.
*
*
*/
@Injectable()
export class HttpClientTestingBackend implements HttpBackend, HttpTestingController {
/**
* List of pending requests which have not yet been expected.
*/
private open: TestRequest[] = [];
/**
* Handle an incoming request by queueing it in the list of open requests.
*/
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
return new Observable((observer: Observer<any>) => {
const testReq = new TestRequest(req, observer);
this.open.push(testReq);
observer.next({type: HttpEventType.Sent} as HttpEvent<any>);
return () => {
testReq._cancelled = true;
};
});
}
/**
* Helper function to search for requests in the list of open requests.
*/
private _match(match: string|RequestMatch|((req: HttpRequest<any>) => boolean)): TestRequest[] {
if (typeof match === 'string') {
return this.open.filter(testReq => testReq.request.urlWithParams === match);
} else if (typeof match === 'function') {
return this.open.filter(testReq => match(testReq.request));
} else {
return this.open.filter(
testReq => (!match.method || testReq.request.method === match.method.toUpperCase()) &&
(!match.url || testReq.request.urlWithParams === match.url));
}
}
/**
* Search for requests in the list of open requests, and return all that match
* without asserting anything about the number of matches.
*/
match(match: string|RequestMatch|((req: HttpRequest<any>) => boolean)): TestRequest[] {
const results = this._match(match);
results.forEach(result => {
const index = this.open.indexOf(result);
if (index !== -1) {
this.open.splice(index, 1);
}
});
return results;
}
/**
* Expect that a single outstanding request matches the given matcher, and return
* it.
*
* Requests returned through this API will no longer be in the list of open requests,
* and thus will not match twice.
*/
expectOne(match: string|RequestMatch|((req: HttpRequest<any>) => boolean), description?: string):
TestRequest {
description = description || this.descriptionFromMatcher(match);
const matches = this.match(match);
if (matches.length > 1) {
throw new Error(`Expected one matching request for criteria "${description}", found ${
matches.length} requests.`);
}
if (matches.length === 0) {
let message = `Expected one matching request for criteria "${description}", found none.`;
if (this.open.length > 0) {
// Show the methods and URLs of open requests in the error, for convenience.
const requests = this.open.map(describeRequest).join(', ');
message += ` Requests received are: ${requests}.`;
}
throw new Error(message);
}
return matches[0];
}
/**
* Expect that no outstanding requests match the given matcher, and throw an error
* if any do.
*/
expectNone(match: string|RequestMatch|((req: HttpRequest<any>) => boolean), description?: string):
void {
description = description || this.descriptionFromMatcher(match);
const matches = this.match(match);
if (matches.length > 0) {
throw new Error(`Expected zero matching requests for criteria "${description}", found ${
matches.length}.`);
}
}
/**
* Validate that there are no outstanding requests.
*/
verify(opts: {ignoreCancelled?: boolean} = {}): void {
let open = this.open;
// It's possible that some requests may be cancelled, and this is expected.
// The user can ask to ignore open requests which have been cancelled.
if (opts.ignoreCancelled) {
open = open.filter(testReq => !testReq.cancelled);
}
if (open.length > 0) {
// Show the methods and URLs of open requests in the error, for convenience.
const requests = open.map(describeRequest).join(', ');
throw new Error(`Expected no open requests, found ${open.length}: ${requests}`);
}
}
private descriptionFromMatcher(matcher: string|RequestMatch|
((req: HttpRequest<any>) => boolean)): string {
if (typeof matcher === 'string') {
return `Match URL: ${matcher}`;
} else if (typeof matcher === 'object') {
const method = matcher.method || '(any)';
const url = matcher.url || '(any)';
return `Match method: ${method}, URL: ${url}`;
} else {
return `Match by function: ${matcher.name}`;
}
}
}
function describeRequest(testRequest: TestRequest): string {
const url = testRequest.request.urlWithParams;
const method = testRequest.request.method;
return `${method} ${url}`;
}