27

I have an angular CLI project set up. I've made a form that uses angular material components, like <md-card>.

I'm just starting out with writing my first Karma/Jasmine unit test, following the steps in the angular docs.

This is my component template:

<md-card [ngClass]="'dialog-card'">
<md-card-title [ngClass]="'dialog-title'">
    {{title}}
</md-card-title>
<md-card-content>

    <form (ngSubmit)="login()" #loginForm="ngForm">

        <md-input-container class="md-block">
            <input md-input [(ngModel)]="user.email" 
                name="userEmail" type="email" placeholder="Email" 
                ngControl="userEmail" 
            required>
        </md-input-container>
        <br>

        <md-input-container class="md-block">
            <input md-input [(ngModel)]="user.password" 
                name="userPassword" type="password" placeholder="Password" 
                ngControl="userPassword" 
            required>
        </md-input-container>
        <br>

        <tm-message msgText="Wrong username or password" *ngIf="showError"></tm-message>
        <br>


        <button md-button type="submit" [disabled]="!loginForm.form.valid">Login</button>

        <p (click)="openForgotPasswordModal()">Forgot Password?</p>

    </form>

</md-card-content>

This is my karma spec:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By }              from '@angular/platform-browser';
import { DebugElement }    from '@angular/core';
import { MaterialModule, MdDialogRef, MdDialog  } from '@angular/material';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';

import { TmLoginComponent } from './tm-login.component';
import { TmMessageComponent } from '../../shared/components/tm-message.component';
import { UserAuthenticationService } from '../login/user-authentication.service';

describe('TmLoginComponent (inline template)', () => {

let comp: TmLoginComponent;
let fixture: ComponentFixture < TmLoginComponent > ;
let de: DebugElement;
let el: HTMLElement;

beforeEach(() => {
    TestBed.configureTestingModule({
        declarations: [TmLoginComponent, TmMessageComponent], // declare the test component
        imports: [MaterialModule, FormsModule,
            RouterTestingModule.withRoutes(
                [{
                    path: 'login',
                    component: TmLoginComponent
                }, ])
        ],
        providers: [UserAuthenticationService],

    });

    fixture = TestBed.createComponent(TmLoginComponent);

    comp = fixture.componentInstance; // TmLoginComponent test instance

    // query for the title <h1> by CSS element selector
    de = fixture.debugElement.query(By.css('.title'));
    el = de.nativeElement;
});

    it('should display original title', () => {
        fixture.detectChanges();
        expect(el.textContent).toContain(comp.title);
    });
});

At this point, I'm just trying to run the basic unit test that the title is being displayed properly.

However, I'm getting a lot of material specific errors. Like

No provider for MdDialog.

I'm opening an md Dialog on clicking a link. THe code is in the (fairly long) .ts file, but that's not the issue here.

Where would I add MdDialog in the testbed? If I add it to providers, I get the error: "no provider for overlay". I don't know how to fix that.

Is there any way I can configure karma to include all material components at start?

Thanks.

isherwood
  • 58,414
  • 16
  • 114
  • 157
Snowman
  • 2,465
  • 6
  • 21
  • 32

3 Answers3

15

Current technique calls for individual imports of Angular Material modules, as MaterialModule is deprecated and was removed in 2.0.0-beta.11:

import {
    MatButtonModule,
    MatIconModule
} from '@angular/material';

Then add the same list as imports in the TestBed config:

beforeEach(async(() => {
    TestBed.configureTestingModule({
        declarations: [ ... ],
        imports: [
            MatButtonModule,
            MatIconModule,
            ...
        ],
        providers: [ ... ]
    })
        .compileComponents();
}));
H. Ruben
  • 3
  • 2
isherwood
  • 58,414
  • 16
  • 114
  • 157
13

All the providers are provided by calling forRoot() on the module

imports: [ MaterialModule.forRoot() ]

For versions 2.0.0-beta.4 and later (since the forRoot method has been removed):

imports: [ MaterialModule ]

For versions 2.0.0-beta.11 and later, since MaterialModule has been removed, you have to import the modules you require for your test cases yourself:

imports: [ MatButtonModule, MatDialogModule ]
Edric
  • 24,639
  • 13
  • 81
  • 91
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thanks for answering. I did that. Now I have `TypeError: Cannot read property 'nativeElement' of null` – Snowman Jan 05 '17 at 11:56
  • I don't see any element (in your template) with a `title` class. I see a `dialog-title` class though – Paul Samsotha Jan 05 '17 at 11:57
  • Do I need an element with a title class? – Snowman Jan 05 '17 at 11:58
  • `de = fixture.debugElement.query(By.css('.title'))`. Why do you think it (`de`) is undefined. – Paul Samsotha Jan 05 '17 at 11:59
  • So sorry. I guess it regressed at some point. Still, I changed the class form `dialog-title` to `title` class and it still threw the same error. Then I created simple `

    {{title}}

    ` and it worked. I don't get it. Is it an issue with `ngClass`?
    – Snowman Jan 05 '17 at 12:12
  • I don't, but why are you using `ngClass` anyway for just a simple string? Just use `class`. If you actually need to evaluate an expression, that's different – Paul Samsotha Jan 05 '17 at 12:15
  • You can always just print out the HTML content of the whole `fixture.nativeElemen.innerHTML`. Maybe that will show you what is actually being rendered (maybe give you a hint as to what's going on) – Paul Samsotha Jan 05 '17 at 12:17
  • BTW, is there anything I could do in the karma conf file so I don't have to import material in each spec file? – Snowman Jan 05 '17 at 12:22
  • Not that I know of. – Paul Samsotha Jan 05 '17 at 12:23
6

I've been struggling with this as well today and you need to mock out the necessary classes yourself by using providers in jasmine. It's a hassle and I wish there was a better way but at least no more errors...

If anyone has a better idea, please enlighten the rest of the community!

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AlertDialogComponent } from './alert-dialog.component';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

describe('AlertDialogComponent', () => {
  let component: AlertDialogComponent;
  let fixture: ComponentFixture<AlertDialogComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [MatDialogModule],
      declarations: [AlertDialogComponent],
      providers: [
        {
          provide: MatDialogRef, useValue: {}
        },
        {
          provide: MAT_DIALOG_DATA, useValue:{}
        }
     ],
    }).compileComponents();
  }));
Peter
  • 14,221
  • 15
  • 70
  • 110