1

I'm new to Angular testing with Karma and I'm not understanding how to properly test a service. Let's say I have this service that I have to test:

    @Injectable()
    export class MyService {
        constructor(private httpService: HttpService) { }

        //methods to test
    }

and this is my test class:

    describe('MyService', () => {
      let service: MyService;
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          providers: [MyService],
        }).compileComponents();
        service = TestBed.get(MyService);
      }));
    });

When I run ng test it gives the error NullInjectorError: No provider for HttpService!.
So I add HttpService into providers array in test class, this way:

    describe('MyService', () => {
      let service: MyService;
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          providers: [MyService, HttpService],
        }).compileComponents();
        service = TestBed.get(MyService);
      }));
    });

HttpService is this:

    @Injectable()
    export class HttpService{
        constructor(private httpService: HttpClient) { }
    }

So now I get the error NullInjectorError: No provider for HttpClient!. (HttpClient is from @angular/common/http).
If I add HttpClient to providers array in test class then I get the same error for all the services/class inside HttpClient constructor, and so on. What is the correct way to instatiate a service, without having to add each providers, leading to potential infinite providers?

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
Usr
  • 2,628
  • 10
  • 51
  • 91

2 Answers2

2

you should use HttpClientTestingModule and mock data with HttpTestingController and the flush method. this is an example with Angular 11 and jasmine 3.6.0 about how I test services. I hope it helps you:

// Dependencies
import { TestBed, waitForAsync } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

// Assets
import { ExampleService } from './api/example.service';

describe('*[Example Service]: -----------------------------------------', () => {
    let httpTestingController: HttpTestingController;
    let service: ExampleService;

    beforeEach(waitForAsync(() => {
        TestBed.configureTestingModule({
            imports: [
             HttpClientTestingModule
            ]
        }).compileComponents();
        
      httpTestingController = TestBed.inject(HttpTestingController);
      service = TestBed.inject(ExampleService);
    }));
    
    it('1). should get data from GET service', waitForAsync( () => {
      // Arrange
      const expectedData: string[] = ['data1', 'data2'];
      let apiError, apiData;
      // Action
      service.getData().subscribe(response => {
        apiData = response;
      }, error => {
        apiError = error;
      });
      const request = httpTestingController.expectOne(`http://localhost:3000/example`);
      request.flush(expectedData);
      // Assert
      expect(apiData.length).toEqual(2);
      expect(apiError).toBeUndefined();
        }));

});
CrgioPeca88
  • 973
  • 7
  • 12
1

In your Testbed configuration, import HttpClientTestingModule

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [MyService, HttpService],
    }).compileComponents();
    service = TestBed.get(MyService);
  }));

You may consider also

  1. changing TestBed.get(MyService); to TestBed.inject(MyService); as TestBed.get(...) is deprecated What's the difference between TestBed.get and new Service(...dependencies)

  2. changing async to waitForAsync as async is deprecated When to use waitForAsync in angular

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • Hi, thank you for your answer. So this solution is specific for my case, but is there a right way to test services in general, without having to import all the services it uses? Like a common way that avoids the import chain? – Usr Mar 24 '21 at 08:17