0

Can someone please help me understand how to unit test the app component below? It's an Angular 2 application.

app.ts

import { Component, OnInit }     from '@angular/core';
import { Router, NavigationEnd, NavigationStart, NavigationCancel, NavigationError, RoutesRecognized } from '@angular/router';

import { DataService }           from '../services/data';

@Component({
  selector: 'app',
  template: '<messages></messages><router-outlet></router-outlet>',
  providers: [DataService]
})
export class AppComponent implements OnInit {

  constructor(private router: Router, private data: DataService) {}

  public ngOnInit(): void {
    this.router.events.subscribe((event: NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized) => {
        if (!(event instanceof NavigationEnd)) { return; }

        document.body.scrollTop = 0;
    });

    window.addEventListener(
        "offline", () => {
            if (this.data.settings.offlineEnabled) {
                this.data.toggleOffline();
                this.data.displayMessage("Internet connection lost, switching to offline mode.", "failure")
            } else {
                this.data.displayMessage("Internet connection lost", "failure")
            }
        }, false,
      );
  }
}

What I've got so far:

app.spec.ts

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app'

import { RouterTestingModule } from '@angular/router/testing';
import { MessagesComponent } from './messages/messages';

import { DataService }           from '../services/data';

/* Tests */
export class MockDataService {}

describe('Component: App', () => {
  let mockData = new MockDataService();
  let component: AppComponent;
  let dataService: DataService;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent,
        MessagesComponent
      ],
      providers: [
        { provide: DataService, useValue: mockData }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    dataService = TestBed.get(DataService);
  }));

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

});

I'm getting the error: Failed: No provider for e! But, it appears to me that I'm importing everything that this component needs for instantiation.

I'm wondering if this is 1) either RouterTestingModule doesn't know router.events.subscribe; or, 2) window is not defined.

Any thoughts?

Thanks!

Edit 1:

It appears that the line that is causing my issues is this:

@Component({
  ...,
  ...,
  providers: [DataService]
})

It appears that Jasmine is injecting the MockDataService object into the app.component constructor, but it's not injecting the object for the provider. Jasmine is providing the concrete DataService implementation.

How do I inject the MockDataService into the providers for the app.component?

a11smiles
  • 1,190
  • 1
  • 9
  • 21
  • I can't speak to your error specifically, but the answers here: https://stackoverflow.com/a/52347301/1433116 ought to help you get better diagnostic information – night_owl Apr 30 '19 at 00:28
  • unfortunately, the original dev didn't use the CLI. – a11smiles Apr 30 '19 at 02:25

1 Answers1

0

It's possible your issue is arising because you are also declaring the MessagesComponent in your unit test. Angular will attempt to new up an instance of this class and if you aren't also injecting mocks for the services MessagesComponent uses then that is likely causing your error.

However, since you are unit testing your AppComponent you will not want to add MessagesComponent to your declarations because, as you are experiencing, everything appears to be working within your AppComponent but a dependency of your component is causing your tests to fail. The easiest way to fix this is to add NO_ERRORS_SCHEMA to your TestingModule like such:

TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
      providers: [
        { provide: DataService, useValue: mockData }
      ],
      schemas: [
        NO_ERRORS_SCHEMA
      ]
    }).compileComponents();

This will prevent the Angular compiler from complaining about custom HTML tags in your code like the tag.

Kaleb Luse
  • 28
  • 3
  • I've found more info. please see the edit to the question – a11smiles Apr 30 '19 at 02:45
  • 2
    Ahh, I see. The way you had it before was correct however since you are injecting the service in the providers of your component rather than the app.module.ts the provider in your component takes precedence over anything you've declared globally which is what your are doing in your configureTestingModule. This should should clarify how to mock a service provided in a component. (https://stackoverflow.com/q/40021366/10414411) – Kaleb Luse Apr 30 '19 at 03:03
  • that was it. I new the component-scoped provider took precedence over the global providers...i just didn't know how to inject them. Thanks so much! – a11smiles Apr 30 '19 at 03:11