angulartypescriptangular2-observablesangular2-testingkarma-coverage

Why the angular unit test is not testing the content of the subscription in this service?


I'm using Angular 14. I'm trying to test this service:

import { EnvironmentService } from './environment.service';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
import { InteractionStatus, RedirectRequest, EventMessage, EventType } from '@azure/msal-browser';
import { Subject, filter, takeUntil } from 'rxjs';
import { Inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

const GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0/me';

/**
 * Every method is disabled if the environment is for development
 */
@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {

  public isIframe: boolean = false;
  public loginDisplay: boolean = !this._environmentService.getEnvironment().production;
  public userLogged$: Subject<boolean> = new Subject<boolean>();
  private readonly _destroying$ = new Subject<void>();
  public profile: any = {
    displayName: "Dev Mode User"
  };

  constructor(
    @Inject(MSAL_GUARD_CONFIG) private _msalGuardConfig: MsalGuardConfiguration,
    private _msalService: MsalService,
    private _msalBroadcastService: MsalBroadcastService,
    private _httpClient: HttpClient,
    private _environmentService: EnvironmentService
  ) { }

  /**
   * Send thorugh observable the information if the user logged or not
   */
  setLoginDisplay() {
    console.log("setLoginDisplay called");
  }

  /**
   * Redirec to login display
   */
  loginRedirect(): void {
    if (this._environmentService.getEnvironment().production) {
      this._msalService.loginRedirect().subscribe(
        () => {
          this.setLoginDisplay();
        }
      );
    }
  }

}

In other cases where the observable was a property of a dependency it worked, but not here for some reason because I keep getting this error: Expected spy setLoginDisplay to have been called.. By checking the coverage I noticed that the test wasn't considering the content of the subscription: enter image description here

This is the corresponding spec.ts file I tried:

import { EnvironmentService } from './environment.service';
import { Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG } from '@azure/msal-angular';
import { MockService } from 'ng-mocks';
import { TestBed, tick } from '@angular/core/testing';

import { AuthenticationService } from './authentication.service';
import { AccountInfo, EventMessage, InteractionStatus } from '@azure/msal-browser';

const mockInstance: any = {
  enableAccountStorageEvents(): void { },
  getAllAccounts(): AccountInfo[] { return [] },
  getActiveAccount(): AccountInfo | null { return null },
  setActiveAccount(account: AccountInfo | null): void { }
}


describe('AuthenticationService', () => {
  let service: AuthenticationService;

  const mockMsalService = MockService(MsalService, {
    instance: mockInstance
  });
  const mockMsalBroadcastService = MockService(MsalBroadcastService, {
    msalSubject$: new Observable<EventMessage>(),
    inProgress$: new Observable<InteractionStatus>(),
  });
  const mockHttpClient = MockService(HttpClient);
  const mockEnvironmentService = MockService(EnvironmentService, {
    getEnvironment() { return { production: true } }
  });

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        {
          provide: EnvironmentService,
          useValue: mockEnvironmentService
        },
        {
          provide: MsalService,
          useValue: mockMsalService
        },
        {
          provide: MsalBroadcastService,
          useValue: mockMsalBroadcastService
        },
        {
          provide: HttpClient,
          useValue: mockHttpClient
        },
        {
          provide: MSAL_GUARD_CONFIG,
          useValue: {}
        }
      ]
    });

    mockMsalBroadcastService.msalSubject$ = new Observable<EventMessage>();
    mockMsalBroadcastService.inProgress$ = new Observable<InteractionStatus>();
  });

  it('should call setLoginDisplay', fakeAsync(() => {
    service = TestBed.inject(AuthenticationService);
    const setLoginDisplaySpy = spyOn(service, "setLoginDisplay").and.callFake(() => { });
    const loginRedirectSpy = spyOn(mockMsalService, "loginRedirect").and.returnValue(new Observable<void>());
    service.loginRedirect();
    tick();
    expect(setLoginDisplaySpy).toHaveBeenCalled();

  }));


});

Can someone help me and maybe explain to me why this happens? Thank you in advance! ^^


Solution

  • I think the new Observable() in your spied mockMsalService does not emit any value. Your subscription is therefore never executed even when you wait one tick. You might want to use of(undefined) or of({}) instead:

    const loginRedirectSpy = spyOn(mockMsalService, "loginRedirect").and.returnValue(of(undefined));
    

    Since of works synchronously you might even remove the tick() from your test.