samedi 23 mars 2019

Testing a component is using the original service intead of the mock service

I'm setting up the unit tests for sign-in component which calls the AuthService when sign-in button is clicked. When I do test that the button have been clicked and this calls the onSignIn() method on the component this test pass. But when I try to test that the onSignIn() have to call signIn() method on the AuthService, the test not pass and call the original service and tries to do a network call instead of use the MockService I have declared. For mock the service I'm using spies.

Here is my code:

sign-in.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { AuthService } from '../../core';
import { AlertService, FormErrorHandler } from '../../shared';

@Component({
  selector: 'app-sign-in',
  templateUrl: './sign-in.component.html',
  styleUrls: ['./sign-in.component.scss']
})
export class SignInComponent implements OnInit {
  signInForm!: FormGroup;
  ...

  constructor(
    private formBuilder: FormBuilder,
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private authService: AuthService,
    private alertService: AlertService
  ) {}

  ngOnInit() {
    ...
  }
  ...

  onSignIn() {
    ...

    this.authService
      .signIn(this.signInForm.value)
      .subscribe(
        user => {
          this.authService.setSession(user, this.rememberMe.value);
          this.router.navigate([this.returnUrl]);
        },
        error => {
          if (error instanceof Object) {
            FormErrorHandler.errorHandler(this.signInForm, error);
          } else {
            this.alertService.error(error);
          }
        }
      );
  }
}

sign-in.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthRoutingModule } from './auth-routing.module';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { FormsModule, ReactiveFormsModule  } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from '../shared';
import { AuthComponent } from './auth.component';
import { SignInComponent } from './sign-in/sign-in.component';
import { SignUpComponent } from './sign-up/sign-up.component';
import { PasswordRecoverComponent } from './password-recover/password-recover.component';
import { PasswordResetComponent } from './password-reset/password-reset.component';

@NgModule({
  declarations: [
    AuthComponent,
    SignInComponent,
    SignUpComponent,
    PasswordRecoverComponent,
    PasswordResetComponent
  ],
  imports: [
    CommonModule,
    AuthRoutingModule,
    SharedModule,
    FontAwesomeModule,
    ReactiveFormsModule,
    FormsModule,
    HttpClientModule,
  ]
})
export class AuthModule {}

auth.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { map } from 'rxjs/operators';
import { config } from '../../../config';
import { User } from '../../../shared';
import { Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthService {
  public isAuthenticated = false;
  public authStatusSubject = new Subject<boolean>();
  public currentUserId = '';
  public currentUserToken = '';

  get id() { return localStorage.getItem('id'); }

  get token() { return localStorage.getItem('token'); }

  get expiration() { return localStorage.getItem('expires_at'); }

  getAuthStatusSubject() { return this.authStatusSubject.asObservable(); }

  constructor(private http: HttpClient, private router: Router) {}
  ...

  signIn(user: User) {
    return this.http.post<{
        message: string;
        user: { id: string; token: string; expiresAt: string };
      }>(`${config.URL}/api/auth/login`, {
        email: user.email,
        password: user.password
      })
      .pipe(map(data => { return data.user; }));
  }

  setSession(user, rememberMe: boolean) {
    if (rememberMe) {
      const expiresAt = new Date(user.expiresAt);
      localStorage.setItem('id', user.id);
      localStorage.setItem('token', user.token);
      localStorage.setItem('expires_at', expiresAt.toUTCString());
    }
    this.isAuthenticated = true;
    this.authStatusSubject.next(true);
    this.currentUserId = user.id;
    this.currentUserToken = user.token;
  }
  ...
}

The AuthService is provided in the core.module

core.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

import { HeaderComponent } from './components';
import { AuthService, UserService } from './services';

@NgModule({
  declarations: [ HeaderComponent ],
  imports: [
    CommonModule,
    RouterModule,
    FontAwesomeModule,
    NgbModule,
  ],
  providers: [
    AuthService,
    UserService
  ],
  exports: [ HeaderComponent ]
})
export class CoreModule { }

sign-in.component.spec.ts

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AbstractControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { CoreModule } from '../../core';
import { AlertService } from '../../shared';
import { SignInComponent } from './sign-in.component';

describe('SignInComponent', () => {
  let signInComponent: SignInComponent;
  let fixture: ComponentFixture<SignInComponent>;
  let signInButton: any;
  let authService: AuthService;
  ...

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [SignInComponent],
      imports: [
        HttpClientTestingModule,
        FormsModule,
        ReactiveFormsModule,
        RouterTestingModule,
        FontAwesomeModule,
        CoreModule,
        SharedModule
      ]
    })
      .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(SignInComponent);
        signInComponent = fixture.componentInstance;
        signInComponent.ngOnInit();
      });
  }));

  beforeEach(() => {
    fixture.detectChanges();
  });

  it('should be created', () => {
    expect(signInComponent).toBeTruthy();
  });

  describe('#form', () => {
    describe('#signInButton', () => {
      beforeEach(() => {
        signInButton = fixture.debugElement.query(By.css('button[type=submit]')).nativeElement;
      });

      it('should render button for submit form', () => {
        expect(signInButton).toBeTruthy();
      });

      it('should sign in when submited', () => {
        spyOn(signInComponent, 'onSignIn'); // This works
        spyOn(authService, 'signIn');

        signInForm.triggerEventHandler('submit', null); // This works

        expect(signInComponent.onSignIn).toHaveBeenCalled(); // This test pass
        expect(authService.signIn).toHaveBeenCalled(); // This test fails
      });
    });
  });
});

Aucun commentaire:

Enregistrer un commentaire