Issue with getting mocked value from service which returns observable in a component

Issue

I am new to writing Jasmine test cases. I have created LoginComponent which will then use AuthService to check the server and return the observable based on the response I will decide and display information appropriately.

As part of writing unit test cases for this component, I need to test valid user and invalid user login.

Scenario1: Invalid login

login method calls AuthService.login() which will be subscribed and if error the message will be displayed on the login page.

Scenario2: Valid Login
Here if the resp contains some token we will consider it as authenticated and route to the home screen.

I tried to check the Invalid scenario and facing issues.

login.component.ts

import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthService } from '../service/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  
  constructor(private authService: AuthService, private router:Router, private activeRoute:ActivatedRoute) { }

  email:string;
  password:string;
  message:string; 
  ngOnInit() {
    
  }

  submitForm()
  {
    this.authService.login({username: this.email, password: this.password}).subscribe
    (
        (resp) =>
        {
          if(resp['status'] == 200 )
          {
            sessionStorage.setItem('UserAuthenticated', 'true');
            sessionStorage.setItem('Jwt_Token', resp['JWT_Token']);
            this.router.navigate(['/dashboard'])
          }
        },
        (errorResp: HttpErrorResponse) =>
        {
          console.log(errorResp)
          this.message = errorResp['error']['errorMessage'];
        }
    );
  }
}

auth.service.ts

import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { environment } from 'src/environments/environment';
import { URLMapping } from '../shared/URLMapping';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor(private http: HttpClient) { }

  login(data:object)
  {
    return this.http.post(environment.applicationURL+URLMapping.LOGIN, data);    
  }

}

login.component.spec.ts

import { HttpClientModule, HttpErrorResponse } from '@angular/common/http';
import { DebugElement } from '@angular/core';
import { async, ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { AuthService } from '../service/auth.service';

import { LoginComponent } from './login.component';

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let authService: AuthService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      imports: [FormsModule, HttpClientModule, RouterTestingModule],
      providers: [AuthService]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    fixture.autoDetectChanges();
  });


  it('invalid user access', async(() => {
    authService = fixture.debugElement.injector.get(AuthService);
    authService = TestBed.get(AuthService);

    const respObj =new BehaviorSubject( new HttpErrorResponse({
      error: { errorMessage: "Invalid credentials"}
    }));
   
    component.email = 'vadine.asyw@infas.com';
    component.password = 'pasywdja';
    fixture.detectChanges();

    spyOn(component, 'submitForm').and.callThrough();
    spyOn(authService, 'login').and.returnValue(of(respObj));


    component.submitForm();
    fixture.detectChanges();
    fixture.whenStable().then( () => {
      
      expect(component.submitForm).toHaveBeenCalled();
      expect(authService.login).toHaveBeenCalled();

      let msgElem = fixture.debugElement.nativeElement.querySelector("#message");
      expect(msgElem).not.toBeNull();
      console.log("text verify"+msgElem.innerHTML)
      expect(msgElem.innerHTML).toContain("Invalid credentials")
    })
    


  }));


  it('route to home when valid login', async(() => {
        //routing to be tested when valid service response is
        {jWT_Token: "y87y21skj.dh8712haskhdaksdj.haasdyusd", status:200}
  }));
  
});

login.compoonent.html

<div class="container md-6">
    <div class="container md-6">
        <div class="container md-6">
            <h1> Employee Management System</h1>
            <form (ngSubmit)= 'submitForm()' #loginForm='ngForm'> 
                <div class="form-group">
                  <label for="email">Email address</label>
                  <input type="email" class="form-control" id="email" aria-describedby="emailHelp" placeholder="Enter email"  
                    name='email' [(ngModel)] ='email' required pattern = "[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$" #useremail="ngModel" >
                  <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
                </div>
                <div class="form-group">
                  <label for="password">Password</label>
                  <input type="password" class="form-control" id="password" placeholder="Password" name='password' required [(ngModel)] = 'password' #loginPassword="ngModel">
                </div>
                <div *ngIf="(useremail.invalid && (useremail.dirty || useremail.touched)) || ( loginPassword.touched && loginPassword.invalid)" id="invalidDetails" class="alert alert-danger" role="alert">
                  Please Enter valid details.
                </div>
                <div *ngIf="message" id="message" class="alert alert-danger" role="alert">
                 {{message}}
                </div>
                <button type="submit" class="btn btn-primary d-flex justify-content-center" [disabled] = '!loginForm.form.valid'>Submit</button>
            </form>
        </div>
    </div>
    

</div>

Any help or suggestions is greatly appreciated.

Solution

You almost had it.

Make the following changes and you should hopefully be good to go (follow the comments):

 it('invalid user access', async(() => {
    // get rid of 1 of the 2 lines here. They both do the same thing
    // I got rid of the first one
    // authService = fixture.debugElement.injector.get(AuthService);
    authService = TestBed.get(AuthService);
    // this should not have BehaviorSubject (it should just be an object)
    const respObj = new HttpErrorResponse({
      error: { errorMessage: "Invalid credentials"}
    });
   
    component.email = 'vadine.asyw@infas.com';
    component.password = 'pasywdja';
    fixture.detectChanges();

    spyOn(component, 'submitForm').and.callThrough();
    // this should not be of, but it should be throwError
    // use of for successful observable
    // import { throwError } from 'rxjs';
    spyOn(authService, 'login').and.returnValue(throwError(respObj));


    component.submitForm();
    fixture.detectChanges();
    fixture.whenStable().then(() => {
      
      expect(component.submitForm).toHaveBeenCalled();
      expect(authService.login).toHaveBeenCalled();

      let msgElem = fixture.debugElement.nativeElement.querySelector("#message");
      expect(msgElem).not.toBeNull();
      console.log("text verify"+msgElem.innerHTML)
      expect(msgElem.innerHTML).toContain("Invalid credentials");
    })
    


  }));

I think that will help you test the failure route ^.

To test the success route, it is the same thing but instead of using throwError, use of.

Answered By – AliF50

Answer Checked By – Jay B. (AngularFixing Admin)

Leave a Reply

Your email address will not be published.