Reputation: 651
I have a Sidenav Menu made in Angular. The component looks like this:
The template:
<mat-toolbar color="primary" [fxLayoutAlign]="(settings.menuType != 'mini') ? 'space-between center' : 'center center'" class="sidenav-header">
<a mat-raised-button color="accent" routerLink="/" (click)="closeSubMenus()" class="small-logo">Menu</a>
<a *ngIf="settings.menuType == 'default'" class="logo" routerLink="/" (click)="closeSubMenus()">mEMS</a>
</mat-toolbar>
<div fxLayout="column" fxLayoutAlign="center center" class="user-block transition-2" [class.show]="settings.sidenavUserBlock">
<div [fxLayout]="(settings.menuType != 'default') ? 'column' : 'row'"
[fxLayoutAlign]="(settings.menuType != 'default') ? 'center center' : 'space-around center'" class="user-info-wrapper">
<div class="user-info">
<p class="name"> {{currentUser}}</p>
<p *ngIf="settings.menuType == 'default'" class="position">Rocket Scientist<br> </p>
</div>
</div>
<div *ngIf="settings.menuType != 'mini'" fxLayout="row" fxLayoutAlign="space-around center" class="w-100 muted-text">
<a mat-icon-button (click)="logout();" routerLink="/login">
<mat-icon>power_settings_new</mat-icon>
</a>
</div>
</div>
<div id="sidenav-menu-outer" class="sidenav-menu-outer" perfectScrollbar [class.user-block-show]="settings.sidenavUserBlock">
<span *ngIf="!menuItems">loading....</span>
<app-vertical-menu [menuItems]="menuItems" [menuParentId]="0"></app-vertical-menu>
</div>
The actual Component:
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { AppSettings } from '../../../app.settings';
import { Settings } from '../../../app.settings.model';
import { MenuService } from '../menu/menu.service';
import { AuthService } from '../../../auth.service';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-sidenav',
templateUrl: './sidenav.component.html',
styleUrls: ['./sidenav.component.scss'],
encapsulation: ViewEncapsulation.None,
providers: [ MenuService ]
})
export class SidenavComponent implements OnInit {
currentUser: String;
public userImage= '../assets/img/users/user.jpg';
public menuItems:Array<any>;
public settings: Settings;
constructor(public authService: AuthService, public appSettings:AppSettings, public menuService:MenuService,public router:Router,){
this.settings = this.appSettings.settings;
}
logout() {
this.authService.logout();
}
ngOnInit() {
let jwt = localStorage.getItem(AuthService.USER_TOKEN_KEY);
let jwtData = jwt.split('.')[1]
let decodedJwtJsonData = window.atob(jwtData)
let decodedJwtData = JSON.parse(decodedJwtJsonData)
console.log(decodedJwtData);
this.currentUser = decodedJwtData.sub;
this.menuItems = this.menuService.getVerticalMenuItems();
}
}
My test is the default basic one:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SidenavComponent } from './sidenav.component';
import { MatButtonModule, MatFormFieldModule, MatInputModule, MatRippleModule, MatCardModule, MatSidenavModule, MatToolbarModule, MatIconModule } from '@angular/material';
describe('SidenavComponent', () => {
let component: SidenavComponent;
let fixture: ComponentFixture<SidenavComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SidenavComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SidenavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Every time I am running this test I am getting these errors because of Material components
Can't bind to 'fxLayoutAlign' since it isn't a known property of 'mat-toolbar'.
1. If 'mat-toolbar' is an Angular component and it has 'fxLayoutAlign' input, then verify that it is part of this module.
2. If 'mat-toolbar' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
1. If 'app-vertical-menu' is an Angular component, then verify that it is part of this module.
2. If 'app-vertical-menu' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("-block-show]="settings.sidenavUserBlock">
<span *ngIf="!menuItems">loading....</span>
All of the above mentioned coponents I have added into app.module.ts
import { MatButtonModule, MatFormFieldModule, MatInputModule, MatRippleModule, MatCardModule, MatSidenavModule, MatToolbarModule, MatIconModule } from '@angular/material';
and exported them ... so I am a bit lost, I don't understand what I am doing wrong of if I should be importing/exporting these modules differently.
Upvotes: 1
Views: 2875
Reputation: 4071
The error is pretty clear, your component doesn't understand the flexLayout
directives because you didn't import it in the test module.
import { FlexLayoutModule } from '@angular/flex-layout';
What you are lacking is the understanding of the test module. Basically, it is used to re-create the context of your component, to be able to set aside the whole dependencies Angular is using in the background. That's why you need to re-import the modules you are using in your original module that declares your component.
For example, here is one of my test module :
TestBed.configureTestingModule({
imports: [
CommonModule,
BrowserAnimationsModule,
ReactiveFormsModule,
MaterialModule,
FlexLayoutModule,
RouterTestingModule,
],
declarations: [
SomeComponent
],
providers: [
// Some examples of stubs used
{ provide: SomeService, useClass: SomeServiceStub },
{ provide: MatDialogRef, useValue: {} },
{ provide: ActivatedRoute, useValue: { 'params': Observable.from([{ 'id': 1 }]) } }
],
schemas: [
NO_ERRORS_SCHEMA
]
});
Your second error is because your component probably uses the app-vertical-menu
component, and since you don't want to have that in your unit test, you should use the NO_ERRORS_SCHEMA
declaration in your testing module.
If you want to write more complex tests, such as integration tests, you can define another test suite with another testing module, that will embark both component (you will have to declare both).
Here is an example :
describe('Integration Test of SomeComponent', () => {
// fixtures and stuff declaration
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
BrowserAnimationsModule,
ReactiveFormsModule,
MaterialModule,
FlexLayoutModule,
RouterTestingModule,
],
declarations: [
SomeComponent,
OtherComponent // Here the other one is declared
],
providers: [
{ provide: SomeService, useClass: SomeServiceStub }, {
provide: MatDialogRef, useValue: {}
}
]
// No more NO_ERROR_SCHEMA here
});
fixture = TestBed.createComponent(SomeComponent);
componentInstance = fixture.componentInstance;
fixture.detectChanges();
});
it('should create component', () => {
expect(componentInstance).toBeTruthy();
});
})
Hope this helps.
EDIT : Here is an example of my component that uses the ServiceStub instead of the service. Instead of actually calling getTheme() and do the HTTP call, I use the stub that overrides this method and returns static data. This allows to avoid having a full service with the Angular HTTP dependency, which may also have other internal Angular Dependencies and such.
// Outside of your test class
export class SomeServiceStub {
constructor() {}
getTheme(): Observable<Theme[]> {
const themes: Theme[] = [
{
id: 128,
code: 'EEE',
libelle: 'Fake label 1'
}, {
id: 119,
code: 'DDD',
libelle: 'Fake label 2'
}
];
return Observable.of(themes);
}
// Other services methods to override...
}
// In your testing module :
providers: [
{ provide: SomeService, useClass: SomeServiceStub },
{ provide: MatDialogRef, useValue: {} }
]
Upvotes: 6