Модульный тест службы с параметром HttpClient поднять "Ошибка типа: _this.handler.handle не является функцией"
Я создал класс, который является инъекционным сервисом, и я хотел бы протестировать функции, которые возвращают объект Observable.
Как только я пытаюсь протестировать такую функцию, я получаю следующую ошибку:
TypeError: _this.handler.handle is not a function
Как я могу проверить эту функцию?
Я нашел много примеров в Интернете, но большинство берут старый модуль Http, который устарел.
Вот мой инъекционный класс:
client.service.ts
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
const BACKEND_PAGINATION_LIMIT = 25;
@Injectable()
/**
* Class who make requests on Alignak backend
* Injectable service
*/
export class BackendClient {
token: string;
url: string;
http: HttpClient;
/**
* @param {HttpClient} http - http client for requests
*/
constructor(http: HttpClient) {
this.http = http;
this.updateData()
}
/**
* Update data of backend: {@link url} and {@link token}
*/
private updateData(){
this.token = localStorage.getItem('token');
this.url = localStorage.getItem('url');
}
/**
* GET http function
* @param {string} endpoint - endpoint of request
* @param {HttpParams} params - http parameters of request
* @param {HttpHeaders} headers - htt headers of request
* @returns {Observable<Object>} - observable object
*/
private get(endpoint: string, params?: HttpParams, headers?: HttpHeaders): Observable<Object> {
this.updateData();
if (headers == null){
headers = new HttpHeaders()
.set('Accept', 'application/json')
.set('Authorization', this.token);
}
return this.http.get(
this.url + '/' + endpoint, {headers, params}
)
}
/**
* POST http function
* @param {string} endpoint - endpoint of request
* @param {Object} body - jsonable object to post
* @returns {Observable<Object>} - observable object
*/
private post(endpoint: string, body: Object): Observable<Object> {
return this.http.post(this.url + '/' + endpoint, body)
}
/**
* Post on "login" endpoint
* @param {string} username - username of backend
* @param {string} password - password of backend
* @returns {Observable<Object>} - observable object
*/
public login(username: string, password: string): Observable<any> {
let body = {
username: username,
password: password
};
return this.post('login', body)
}
}
И соответствующий тест:
client.service.spec.ts
import {async, TestBed} from '@angular/core/testing';
import {HttpClient, HttpHandler} from "@angular/common/http";
import {BackendClient} from "./client.service";
describe('BackendClient Service', () => {
let client: BackendClient;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [BackendClient, HttpClient, HttpHandler],
});
}));
beforeEach(() => {
localStorage.setItem('url', '');
localStorage.setItem('token', '');
client = TestBed.get(BackendClient);
});
// This test works
it('Init BackendClient', () => {
expect(client.token).toEqual('');
expect(client.url).toEqual('');
expect(client.http instanceof HttpClient).toBe(true);
});
// This test fails and expect is not take in account
it('Login to Backend', () => {
client.url = 'http://demo.alignak.net:5000';
client.login('admin', 'admin')
.subscribe(
function (data) {
console.log('Received data: ', data);
expect(data['token'] != undefined).toBe(true)
},
err => console.log('Request ERR: ', err)
)
})
});
Вот полная ошибка вывода:
....LOG: 'Request ERR: ', TypeError: _this.handler.handle is not a function
TypeError: _this.handler.handle is not a function
at MergeMapSubscriber.project (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:66308:219)
at MergeMapSubscriber._tryNext (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:104576:27)
at MergeMapSubscriber._next (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:104566:18)
at MergeMapSubscriber.Subscriber.next (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:36128:18)
at ScalarObservable._subscribe (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:128360:24)
at ScalarObservable.Observable._trySubscribe (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:23081:25)
at ScalarObservable.Observable.subscribe (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:23069:93)
at MergeMapOperator.call (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:104541:23)
at Observable.subscribe (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:23066:22)
at FilterOperator.call (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:137708:23)
at Observable.subscribe (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:23066:22)
at MapOperator.call (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:130125:23)
at Observable.subscribe (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:23066:22)
at UserContext.<anonymous> (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:138452:14)
at ZoneDelegate.invoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123739:26)
at ProxyZoneSpec.onInvoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:126726:39)
at ZoneDelegate.invoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123738:32)
at Zone.run (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123489:43)
at runInTestZone (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:126987:34)
at UserContext.<anonymous> (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:127002:20)
at ZoneQueueRunner.attempt (http://localhost:9876/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?daba65c98fa088349a3e9d7df843a63405ccfc15:4816:44)
at ZoneQueueRunner.QueueRunner.run (http://localhost:9876/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?daba65c98fa088349a3e9d7df843a63405ccfc15:4854:25)
at runNext (http://localhost:9876/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?daba65c98fa088349a3e9d7df843a63405ccfc15:4784:18)
at next (http://localhost:9876/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?daba65c98fa088349a3e9d7df843a63405ccfc15:4791:11)
at http://localhost:9876/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?daba65c98fa088349a3e9d7df843a63405ccfc15:4709:12
at http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:52171:17
at ZoneDelegate.invoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123739:26)
at AsyncTestZoneSpec.onInvoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:127212:39)
at ProxyZoneSpec.onInvoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:126723:39)
at ZoneDelegate.invoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123738:32)
at Zone.run (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123489:43)
at AsyncTestZoneSpec.finishCallback (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:52166:25)
at http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:127155:31
at ZoneDelegate.invokeTask (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123772:31)
at Zone.runTask (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123539:47)
at ZoneTask.invokeTask (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123847:34)
at ZoneTask.invoke (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:123836:48)
at timer (http://localhost:9876/base/test-config/karma-test-shim.js?c3c7bb07d085cf3d6c123f55b228352de66aee6a:125405:29)
Похоже, что обработчик клиента HttpClient не определен... и тест в подписке не учитывается.
Решение с помощью Mock:
import {TestBed, getTestBed} from '@angular/core/testing';
import {HttpClient} from "@angular/common/http";
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import {BackendClient} from "./client.service";
describe('BackendClient Service', () => {
let injector: TestBed;
let service: BackendClient;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [BackendClient]
});
injector = getTestBed();
localStorage.setItem('token', 'my-long-token');
localStorage.setItem('url', 'http://demo.alignak.net:5000');
service = injector.get(BackendClient);
httpMock = injector.get(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('Init BackendClient', () => {
expect(service.token).toEqual('my-long-token');
expect(service.url).toEqual('http://demo.alignak.net:5000');
expect(service.http instanceof HttpClient).toBe(true);
});
// This test fails and expect is not take in account
it('Login to Backend', () => {
const dummyToken = [
{ token: 'my-received-token' }
];
service.login('admin', 'admin').subscribe(
token => {
expect(token.length).toBe(1);
expect(token).toEqual(dummyToken);
});
const req = httpMock.expectOne(`${service.url}/login`);
expect(req.request.method).toBe("POST");
expect(req.request.url).toBe(`${service.url}/login`);
expect(req.request.body).toEqual({username: 'admin', password: 'admin'});
req.flush(dummyToken);
})
});
1 ответ
Прежде всего:
http: HttpClient;
constructor(http: HttpClient) {
this.http = http;
}
Это дублированный код. Помещение переменной в качестве параметра в конструктор создает члена класса. Здесь вы создаете это дважды. Я поражен тем, что ваш линтер не говорит вам, что у вас есть теневая переменная.
Во-вторых, когда вы хотите протестировать сервис, который выполняет HTTP-вызовы, вы должны издеваться над своим бэкэндом. Здесь вы не издеваетесь, вы делаете реальные HTTP-звонки. Это не юнит-тестирование.
Если вы не знаете, как издеваться над своим бэкэндом, быстрый поиск в Google даст вам результаты, подобные этому.
Наконец, эта ошибка связана с тем, что у вас есть перехватчик, который перехватывает ваши запросы. Насмешка над вашим бэкэндом избавит от указанного перехватчика, устраняя вашу проблему.