2
votes

I'm currently working on a legacy Angular 5 code that doesn't have any unit testing except the default ones when generating a component using the Angular CLI.

The project is using Angular Material 5 RC0 and it seems that the unit tests doesn't take into account my module's dependencies.

app.module.ts :

@NgModule({
  declarations: [
    AppComponent,
    /* My module components... */
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    ReactiveFormsModule,
    BrowserAnimationsModule,
    routing,
    DialogsModule,
    SharedModule ,
    MaterialModule,
    MatListModule, // It's the module that should be needed
    MatSidenavModule,
    ToastrModule.forRoot({
      timeOut: 5000,
      positionClass: 'toast-bottom-right',
      preventDuplicates: true
    }),
    HttpClientModule,
    TableFactoryModule
  ],
  providers: [
    /* My module services */,
    AuthGuard
  ],
  bootstrap: [AppComponent]

})
export class AppModule {

}

app.component.ts:

import { Component, Input, ViewChild } from '@angular/core';
import { Router, RouterModule, ActivatedRoute } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { JwtHelperService } from '@auth0/angular-jwt';
import * as _ from 'lodash';

import { Subscription } from 'rxjs/Subscription';
import { ToastrService } from 'ngx-toastr';

import { UsersService } from 'app/services/users.service';
import { ComplexesService } from 'app/services/complexes.service';
import { ActivatedUserService } from 'app/shared/activateduser.service';
import { ActivatedComplexesService } from 'app/shared/activatedcomplexes.service';

import { User } from 'app/user';
import { Complex } from './complex/complex'
import { MatSidenav } from '@angular/material';
import { MatList } from '@angular/material/list'


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  isConnected = false;
  private subscription;
  private currentUser: User;
  userSubscription: Subscription;
  complexesSubscription: Subscription;
  @ViewChild(MatSidenav) sidenav: MatSidenav;

  constructor(
    private usersService: UsersService,
    private complexesService: ComplexesService,
    private router: Router,
    private toastr: ToastrService,
    private activatedUser: ActivatedUserService,
    private activatedComplexes: ActivatedComplexesService,
  ){
  }
  ngOnInit() {
    let getToken = this.usersService.isConnected();
    if (getToken) {
      this.getComplexes();
      this.userGetById();      
    }
    else
      this.router.navigate(['']);

    this.userSubscription = this.activatedUser.activatedUser$
    .subscribe(user => {
      if(user) {
        this.currentUser = user;
        this.sidenav.open();
      }
    });
  }
  logout(){
    this.usersService.logout();
    this.isConnected = this.usersService.isConnected();
    this.router.navigate(['']);
  }
  ngOnDestroy() {
    this.userSubscription.unsubscribe();
    this.complexesSubscription.unsubscribe();    
  }

  private userGetById(){
    let token = JSON.parse(localStorage.getItem('futbakCurrentUser'));
    let jwtHelperService: JwtHelperService = new JwtHelperService({});
    let currentUser = jwtHelperService.decodeToken(token);
    this.usersService.getByID(currentUser.id).subscribe(
      (res) => {
        this.currentUser = res
      }, (err: HttpErrorResponse) => {
        if (err.error instanceof Error) {
          // A client-side or network error occurred. Handle it accordingly.
          this.toastr.error("Une erreur est survenue : "+err.error.message  , '', { closeButton: true });
        } else {
          this.toastr.error("Une erreur est survenue code : "+ err.status+", message : "+err.statusText, '', { closeButton: true });
        }
      },
      () => {
        if(this.currentUser.role == 'superadmin' || this.currentUser.role == 'complexmanager') {
          this.activatedUser._activatedUser$.next(this.currentUser)
        }
        if(this.currentUser.role == 'superadmin') {
          this.router.navigate(['usersList']);
          this.getComplexes();
        } else if (this.currentUser.role == 'complexmanager') {
          this.router.navigate(['admins/'+this.currentUser._id])
        } else {
          this.toastr.error("Vous n'avez pas les droits pour accèder à l'application.",'',{closeButton :true});
        }
      });
  }

  private getComplexes(){
   return this.complexesService.get()
    .subscribe(
      res => {
        if(res)
          this.activatedComplexes._activatedComplexes$.next(res)
    }, (err: HttpErrorResponse) => {
        if (err.error instanceof Error) {
          this.toastr.error("Une erreur est survenue : "+err.error.message  , '', { closeButton: true });
        } else {
          this.toastr.error("Une erreur est survenue code : "+ err.status+", message : "+err.statusText, '', { closeButton: true });       
        }
      },
      () => {
      });
  }

 }

The failing test :

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

app.component.html :

<mat-sidenav-container fullscreen >

  <mat-sidenav #sidenav mode="side" disableClose>
    <!-- sidenav content -->

    <div><img src="./assets/logo.png" class="logo" ></div>
    <div class="title-app">
      <h2>Admin</h2>
    </div>

    <mat-nav-list>
      <a mat-list-item routerLinkActive="active" routerLink="['usersList']">
        <!-- <mat-icon fontSet="fa" fontIcon="fa-users" ></mat-icon> -->
        <i class="fa fa-users fa-fw fa-lg"></i> Joueurs
      </a>
      <a mat-list-item routerLink="['adminsList']">
        <i class="fa fa-user fa-fw fa-lg"></i> Admins
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="['gamesList']">
          <i class="fa fa-futbol-o fa-fw fa-lg"></i> Matchs
      </a>
      <a mat-list-item routerLinkActive="active"  routerLink="['playgroundsList']">
        <i class="fa fa-cubes fa-fw fa-lg"></i> Terrains
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="['complexesList']">
        <i class="fa fa-cube fa-fw fa-lg"></i> Complexes
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="['devicesList']">
        <i class="fa fa fa-share-alt fa-fw fa-lg"></i> Capteurs
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="['experimental', 'playgrounds']">
        <i class="fa fa fa-share-alt fa-fw fa-lg"></i> Business
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="['experimental', 'devices']">
        <i class="fa fa fa-share-alt fa-fw fa-lg"></i> Technique
      </a>
    </mat-nav-list>
    <mat-nav-list class="account">
        <button mat-button (click)="logout()"><i class="fa fa-sign-out"></i> Déconnexion</button>
    </mat-nav-list>

  </mat-sidenav>
  <div class="main mat-typography">
    <!-- primary content -->
    <router-outlet ></router-outlet>
  </div>
</mat-sidenav-container>

And finally, the error :

Failed: Template parse errors:
'mat-nav-list' is not a known element:
1. If 'mat-nav-list' is an Angular component, then verify that it is part of this module.
2. If 'mat-nav-list' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
    </div>

    [ERROR ->]<mat-nav-list>
      <a mat-list-item routerLinkActive="active" routerLink="['usersList']">
       "): ng:///DynamicTestModule/AppComponent.html@10:4

So, as you've seen, I've already tried those kind of solution :

and so on.

I only have this problem on the unit testing, the website is running properly.

1

1 Answers

5
votes

Testing requires you to import the modules by hand. It allows for example to load "mockup services" instead of the real ones when you need them.

You will find the corresponding doc here, and connected topics on the same doc page.

So you will have to re-import the needed modules in your test file (inside your describe item), with something like:

beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [ /* Your component */ ],
    imports: [
      /* Here, import your Angular material modules */
    ],
    providers: [
      /* Here, use custom or usual providers for your injected services */
    ]
  })
  .compileComponents();
}));