2

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.

bicarlsen
  • 1,241
  • 10
  • 27
  • I'm not sure why, but you provide your Base service with the token `CrudService`, and then you try to get it from the TestBed using the token `AbstractService`. That's why it's not defined. Why don't you provide and get it, like most services, with the actual class of the service: `Base`? – JB Nizet Aug 17 '19 at 21:50
  • @JBNizet Thanks for spotting those typos. I tried to make an MVE, and missed some of the changes I had to make. – bicarlsen Aug 18 '19 at 05:41
  • 1
    Now you're using useValue, and passing undefined as value. Use useClass: Base – JB Nizet Aug 18 '19 at 06:15
  • That was it! Thanks @JBNizet. If you post as an answer I'll gladly accept it. – bicarlsen Aug 18 '19 at 06:28

0 Answers0