I am trying to test an abstract service class that uses HttpClient
.
To do this I've followed the suggestion in this thread by implementing the most basic instantiation of the abstract class, and trying to test that. However I still can't get it to work.
After several attempts, I am currently using code of the form below.
The abstract class:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Resource } from './models/resource.model';
import { BaseFunctions } from './interfaces/base-functions.interface';
@Injectable({
providedIn: 'root'
})
export abstract class AbstractService<T extends Resource> implements BaseFunctions {
constructor(
public url: string,
public name = 'abstract',
protected http: HttpClient
) {}
get( id: string ): Observable<T> {
return this.http.get<T>( `${this.url}/${id}` );
}
The test code:
import { TestBed } from '@angular/core/testing';
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { AbstractService } from './abstract.service';
import { Resource } from './models/resource.model';
describe('AbstractService', () => {
const service_name = 'test',
url = `/${service_name}`;
let service: AbstractService<Resource>,
httpClient: HttpClient,
httpTestingController: HttpTestingController;
let resource: Resource;
// create class to mock AbstractService
@Injectable()
class Base extends AbstractService<Resource> {
constructor(
protected http: HttpClient
) {
super( url, service_name, http );
}
}
let abs_service: Base;
beforeEach(() => {
resource = {
id: 'id'
};
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
{ provide: AbstractService, useValue: abs_service }
]
});
service = TestBed.get( AbstractService );
httpClient = TestBed.get( HttpClient );
httpTestingController = TestBed.get( HttpTestingController );
} ); // end beforeEach
afterEach( () => {
httpTestingController.verify();
} );
it('should be created', () => {
const service: AbstractService<Resource> = TestBed.get(AbstractService);
expect(service).toBeTruthy();
});
describe( '#get', () => {
it( 'should exist', () => {
expect( service.get ).toBeDefined();
} );
it( `should get ${url}/:id`, () => {
service.get( resource.id )
.subscribe();
const req = httpTestingController.expectOne( `${url}/${resource.id}` );
expect( req.request.method ).toBe( 'GET' );
req.flush( resource );
} );
} ); // end #get
});
This code compiles with no errors, but the tests fail because the service
is not defined. In other versions of the code I got the service
to be defined, but either #get
was not defined, or the httpTestingController.expectOne( `${url}/${resource.id}` );
would fail. I suspect the latter would fail because I was not correctly injecting the HttpClient
into the Base
class.
Question 1: Does the order of parameters to the abstract class's constructor matter? (i.e. should the http
parameter go first, second, third? Does it matter?)
Question 2: How do I implement the Base
class in such a way that I can use the HttpTestingController
?
Also, if you spot any other errors, or my architecture is completely off, please let me know.