Я пытаюсь написать модульные тесты для моей службы API, но у меня есть некоторые проблемы с перехватом ошибок HTTP. Я следую этому руководству вместе с Angular2 docs, так как руководство (немного) устарело в некоторых незначительных областях.
Все модульные тесты проходят отдельно от тех, где служба выдает ошибку (из-за кода состояния HTTP ошибки). Я могу сказать это, выйдя из response.ok
системы . Из того, что я прочитал, это связано с тем, что модульные тесты не выполняются асинхронно, следовательно, не ждут ответа на ошибку. Однако я понятия не имею, почему это происходит здесь, так как я использовал функцию async()
полезности в beforeEach
методе.
Служба API
get(endpoint: string, authenticated: boolean = false): Observable<any> {
endpoint = this.formatEndpoint(endpoint);
return this.getHttp(authenticated) // Returns @angular/http or a wrapper for handling auth headers
.get(endpoint)
.map(res => this.extractData(res))
.catch(err => this.handleError(err)); // Not in guide but should work as per docs
}
private extractData(res: Response): any {
let body: any = res.json();
return body || { };
}
private handleError(error: Response | any): Observable<any> {
// TODO: Use a remote logging infrastructure
// TODO: User error notifications
let errMsg: string;
if (error instanceof Response) {
const body: any = error.json() || '';
const err: string = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''}${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
Модульный тест ошибок
// Imports
describe('Service: APIService', () => {
let backend: MockBackend;
let service: APIService;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
BaseRequestOptions,
MockBackend,
APIService,
{
deps: [
MockBackend,
BaseRequestOptions
],
provide: Http,
useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
},
{provide: AuthHttp,
useFactory: (http: Http, options: BaseRequestOptions) => {
return new AuthHttp(new AuthConfig({}), http, options);
},
deps: [Http, BaseRequestOptions]
}
]
});
const testbed: any = getTestBed();
backend = testbed.get(MockBackend);
service = testbed.get(APIService);
}));
/**
* Utility function to setup the mock connection with the required options
* @param backend
* @param options
*/
function setupConnections(backend: MockBackend, options: any): any {
backend.connections.subscribe((connection: MockConnection) => {
const responseOptions: any = new ResponseOptions(options);
const response: any = new Response(responseOptions);
console.log(response.ok); // Will return false during the error unit test and true in others (if spyOn log is commented).
connection.mockRespond(response);
});
}
it('should log an error to the console on error', () => {
setupConnections(backend, {
body: { error: `Some strange error` },
status: 400
});
spyOn(console, 'error');
spyOn(console, 'log');
service.get('/bad').subscribe(null, e => {
// None of this code block is executed.
expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
console.log("Make sure an error has been thrown");
});
expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown."); // Fails
});
Обновление 1
когда я проверяю первый обратный вызов, ответ.ok не определен. Это наводит меня на мысль, что в утилите что-то не такsetupConnections
.
it('should log an error to the console on error', async(() => {
setupConnections(backend, {
body: { error: `Some strange error` },
status: 400
});
spyOn(console, 'error');
//spyOn(console, 'log');
service.get('/bad').subscribe(res => {
console.log(res); // Object{error: 'Some strange error'}
console.log(res.ok); // undefined
}, e => {
expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
console.log("Make sure an error has been thrown");
});
expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown.");
}));
Обновление 2
Если вместо того, чтобы ловить ошибки в get методе я делаю это явно в map, то все еще есть та же проблема.
get(endpoint: string, authenticated: boolean = false): Observable<any> {
endpoint = this.formatEndpoint(endpoint);
return this.getHttp(authenticated).get(endpoint)
.map(res => {
if (res.ok) return this.extractData(res);
return this.handleError(res);
})
.catch(this.handleError);
}
Обновление 3
После некоторого обсуждения этот вопрос представлен
Вы должны использовать его в тестовом случае (the
it
). Чтоasync
делает, так это создает тестовую зону, которая ожидает завершения всех асинхронных задач перед завершением теста (или тестовой области, напримерbeforeEach
).Таким
async
образом, inbeforeEach
только ждет асинхронных задач для завершения в методе перед выходом из него. Ноit
также нуждается в том же самом.ОБНОВЛЕНИЕ
Помимо отсутствующего
async
, кажется, есть ошибка сMockConnection
. Если посмотретьmockRespond
, то он всегда звонитnext
, не принимая во внимание код состоянияУ них есть
mockError(Error)
метод, который вызываетerror
но этот звонок не позволит вам пройти а
Response
. Это несовместимо с тем, какXHRConnection
работает real, который проверяет статус и отправляетResponse
либо черезnext
orerror
, но является тем же самымResponse
Похоже на жука. То, что вы, вероятно, должны сообщить. Они должны позволить вам либо отправить
Response
mockError
или сделать ту же регистрацию,mockRespond
что и ониXHRConnection
.Обновлено (OP) SetupConnections()
Текущее решение
Вот мое рабочее решение, которое похоже на вышеуказанные предложения, но с большей ясностью:
Ниже приведены все возможные сценарии тестирования.
Примечание: не беспокойтесь о AjaxService . Это моя пользовательская оболочка на angular http service, которая используется в качестве перехватчика.
Аякс.услуга.спекуляция.ts
Аякс.услуга.ts