Overview

When your module depends on multipe other modules(which it will, eventually), you will have to edit your test file of components. Because in the component test file, you create a module from scratch using TestBed. This module has to have the modules which are being used in the component. So if your component using some material-icon module to display the icon, the module in test file also has to import the material icon module, and the material module(because material icon module depends on material module).

You have two ways to escape this

1. Use the CUSTOM_ELEMENTS_SCHEMA which will ignore the custom components used in the template like md-icon etc

Example

import { TestBed, async } from '@angular/core/testing';

import { AppComponent } from './app.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      schemas:[CUSTOM_ELEMENTS_SCHEMA]
    }).compileComponents();
  }));

  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

});

2. Import the app module itself in the TestBed.

Example

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

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        AppModule
      ]
    }).compileComponents();
  }));

  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

});

So which one is good, which one should you prefer? Lets explore each method

1. CUSTOMELEMENTSSCHEMA

It is a constant predefined by angular

export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = {
    name: 'custom-elements'
};

It defines a schema that will allow following in the template:

  • any non-Angular elements with a - in their name,
  • any properties on elements with a - in their name which is the common rule for custom elements.

So if you are using <md-icon> in your template file, then it wont throw it error as it will be ignored.

But its not enough!!

You see, ignoring the custom modules is fine, but not good. If you have used some services in the component file, then you are out of luck. Because you will have to import all those services and also stub them in your tests !!!

For example, if your component looks like this

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    title = 'app';
    constructor(private router:Router){}
}

Then your test will throw the error

Failed: No provider for Router! Error: No provider for Router!

So you will have to stub the router

class RouterStub {
    navigateByUrl(url: string) { return url; }
}

And then use it as

{ provide: Router, useClass:RouterStub }

If you are using some other services like localStorageService which stores and fetches some data from the local storage. You will have to stub it too.

2. Importing AppModule directly

If you directly import the AppModule, then you do not have to stub anything, or use some schema which will ignore the custom elements.

Because AppModule already has those required services and components imported for you.

But

But it is dangerous to import AppModule.

It is dangerous because you are injecting real service instead of fake ones. So every time you are running the test, you are hitting your server ! You may think it might be ok because you are running your server locally, and testing it locally. But what if your team has some continous integration or continous deployment built in, which runs the unit tests before deployment. It will hit your test server to run the unit tests, which is bad. It could be easily avoided if you stub those services.

It is dangerous because it will throw weird errors when you run your test, which are hard to debug. Like this

[object ErrorEvent] thrown

This error occurs because somewhere in your component, you have not handled the error thrown by service. The service throws error because server is not running. Yes you should handle your errors, but finding the error this way is very hard to debug it, and know where in the code went wrong.

It is dangerous because it is kind of shortcut, like duct tape. Sure it works, but its like fixing the real problem with another problem. So stubbing the services and using schemas is the way to go!