[Fixed] Angular Unit Test for Service injecting other Services

Issue

I’ve written Angular tests before, but I’m at a loss on how to create a test that covers the code for the following. I’ll include what I’ve wired up, but the tests are basic.

Here is the service file:

@Injectable()
export class HealthViewService {
    constructor(private readonly healthService: HealthService, private router: Router){}

    createNewPatient() {
       this.healthService.currentStatus = this.healthService.getInitialStatus();
       this.router.navigate('health/patient');
    }
}

And here’s the spec file:

import {HealthViewService} from '<not showing pathway>';
import {HealthService} from '<not showing pathway again>';

const healthViewService = {
    createNewPatient: () => {}
}

const healthServiceStub = { currentStatus: () { return StatusTarget.Inbound; }}

let routerStub = { navigate: () => {} }

describe('HealthViewService', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [{provide: Router, useValue: routerStub }, 
                       {provide: HealthService, useValue: healthServiceStub },
                       {provide: HealthViewService, useValue: healthViewService}]
        })
    });

    describe('createNewPatient', () => {
       it('it should route to patient page', () => {
           expect(healthViewService.createNewPatient).toBeTruthy();
       });
    });
});

The good news is this test passes, but it’s not really a good test for code coverage. My question is: Is there some way that I could properly mock up the router and watch where the router navigates too? And can I spy on the HealthService to check values there as well? Any help would be appreciated.

Solution

Several problems from your code:

  • your spec file does not test your actual implementation of the HealthViewService class because you provide some kind of fake object const healthViewService = { createNewPatient: () => {} } as instance of the service
  • you expect healthViewService.createNewPatient method to exist, which will always be true (you don’t call the method)
  • the router navigate method expect an array of string, not a string

Here are the fixes you may consider (I removed the HealthService part to give you a MWE)

@Injectable()
export class HealthViewService {
  constructor(private router: Router) {}

  createNewPatient() {
    this.router.navigate(["health", "patient"]);
  }
}

and the corresponding spec file

import { TestBed } from "@angular/core/testing";
import { Router } from "@angular/router";
import { HealthViewService } from "./health-view-service.service";

describe("HealthViewServiceService", () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        // provide the actual implementation you want to test
        // shortcut for {provide: HealthViewService, useClass: HealthViewService}
        HealthViewService,
        // provide a mocked Router, injected through inversion of control mechanism offered by angular
        { provide: Router, useValue: { navigate: () => {} } },
      ],
    });
  });

  it("createNewPatient() should navigate to health/patient", () => {
    const service = TestBed.inject(HealthViewService);
    const router = TestBed.inject(Router);
    const spy = spyOn(router, "navigate");

    service.createNewPatient();

    expect(spy).toHaveBeenCalledWith(["health", "patient"]);
  });
});

Leave a Reply

(*) Required, Your email will not be published