Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 7 Component Test is using original service instead of mock

I am trying to test a component that has an injected service. I want to provide a mock service in my test. However the test is using the original service instead of the mock one (I know this because I am getting a "No provider for HttpClient!" error and also I have a console.log in the original service that is outputting in the test).

I can fix the error by importing the HttpClientTestingModule but this doesn't fix the fact that the original service is being used instead of the mock one.

Any ideas what I am doing wrong?

Here is my test code. Angular version 7

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { HelloWorldComponent } from '../../app/components/hello-world/hello-world.component';
import { HelloWorldService } from '../../app/services/hello-world.service';

describe('HelloWorldComponent', () => {

  let component: HelloWorldComponent;
  let fixture: ComponentFixture<HelloWorldComponent>;
  let mockHelloWorldService;
  
  beforeEach(() => {

    mockHelloWorldService = jasmine.createSpyObj(['getHelloWorld']);
    
    TestBed.configureTestingModule({
      imports: [],
      declarations: [HelloWorldComponent],
      providers: [
        [{ provide: HelloWorldService, useClass: mockHelloWorldService }]
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(HelloWorldComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
	
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
  
});

UPDATE

I have tried overrideProvider and now I am now getting a 'Cannot read property 'subscribe' of undefined' error, which sort of feels like progress...

Here's my test code

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { HelloWorldComponent } from '../../app/components/hello-world/hello-world.component';
import { HelloWorldService } from '../../app/services/hello-world.service';
import { of } from 'rxjs';

describe('HelloWorldComponent', () => {

  let component: HelloWorldComponent;
  let fixture: ComponentFixture<HelloWorldComponent>;
  let mockHelloWorldService;

  beforeEach(async(() => {
    mockHelloWorldService = jasmine.createSpyObj(['getHelloWorld']);
    TestBed.configureTestingModule({
      imports: [],
      declarations: [HelloWorldComponent]
    });
    TestBed.overrideProvider(HelloWorldService, { useValue: mockHelloWorldService });
    TestBed.compileComponents();

  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HelloWorldComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Here's my component

import { Component, OnInit } from '@angular/core';
import { HelloWorldService } from '../../services/hello-world.service';

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  providers: [HelloWorldService]
})
export class HelloWorldComponent implements OnInit {

  helloWorldMessage: any;
  constructor(private helloWorldService: HelloWorldService) { }

  ngOnInit() {
    this.getHelloWorldMsg();
  }

  getHelloWorldMsg() {
    this.helloWorldService
      .getHelloWorld()
      .subscribe((data) => {
        this.helloWorldMessage = data;
        }, err => this.handleErrorResponse('There was a problem loading the hello world message', err));
  }

  handleErrorResponse(errorMsg: string, error?: any) {
    console.log("There was a problem getting the message");
  }
}

And here's my service

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import { environment } from '../../environments/environment';

@Injectable()
export class HelloWorldService {
  constructor(private http: HttpClient) { }

  getHelloWorld(): Observable<any> {
    console.log("Why is the test coming here when I provide a mock?");
    var getHelloWorldApiUrl = environment.apiUrl + "/api/v1.0/helloworld/GetHelloWorldMessageAuth";
    return this.http
    .get(getHelloWorldApiUrl);
  }
}
like image 357
Tracey Penberthy Avatar asked Feb 03 '23 20:02

Tracey Penberthy


1 Answers

I see two issues in the code above:

  • You have an extra set of square brackets in the providers array of your TestBed. I don't think this is a major issue, it is just semantically incorrect.
  • When you provide HelloWorldService in the providers array, you have specified useClass, but have provided an object (which is what jasmine.createSpyObj() produces), so you should instead specify useValue.

I hope this helps.

Update:

Ok, you have come quite a long ways! You corrected the two problems I outlined above, have done the overrideProvider() before the compileComponents() as I suggested in a comment to another answer as well as wrapped the beforeEach() inside an async().

The reason I asked to see all the other code was so I could quickly put it up in the following StackBlitz for testing. As you can see in that StackBlitz, the test is now passing.

I only added one line in the beforeEach() to declare a returnValue from your spy, so that the subscribe in your component has an Observable to subscribe to:

mockHelloWorldService.getHelloWorld.and.returnValue(of('Test Message'));

I added this before the call to fixture.detectChanges() since this will invoke ngOnInit(), and the spy needs a return value set BEFORE executing ngOnInit() so that it will execute correctly and not give you the error you were seeing.

I also added a line in the spec to show how you can test that the result of the Observable is correctly set to your component variable:

expect(component.helloWorldMessage).toEqual('Test Message');

Note that you are significantly complicating your testing by specifying the HelloWorldService in the providers array of your component declaration. If you instead provide this service as a singleton in root that will simplify things a lot, including how you test. See details in the Official Docs.

like image 93
dmcgrandle Avatar answered Feb 07 '23 12:02

dmcgrandle