简体   繁体   中英

Angular 7 - Unit test for an observable with subscribe

Hi I am trying to write angular code for a component with an observable but I can't test the subscribe function. How Should I access the subscribe function? Any help would be appreciated. Thank you.

This is my component with the observable:

  ngOnInit(): void {

    if (this.authService.getUser() !== null || this.authService.getUser() !== undefined) {
      console.log('getUser');
      this.userService.userDetails = this.authService.getUser();
    }

    if (this.environmentName === 'QA' || this.environmentName === 'LOCAL' || this.environmentName === 'QA-STAGING') {
      console.log('environmentName');
      this.authService.acquireTokenSilent(['api://012fdc3a-c966-4312-9b5c-301f097c1803/server']);
    } else {
      this.authService.acquireTokenSilent(['api://58a80bb5-906b-4ec0-9b41-7a78a07125af/server']);
    }

    this.subscription.add(
      this.broadcastService.subscribe('msal:acquireTokenSuccess', (payload) => {
        // do something here
        console.log('acquire token success ' + JSON.stringify(payload));

        this.roleService.checkServerEventReviewers().subscribe(res => {
          this.userService.userDetails.role = res ? 'Data Steward' : 'Mosaic Consumer';
          if (this.isLoggedIn !== true) {
            const redirectUri = sessionStorage.getItem('redirectUri');
            if (redirectUri !== undefined || redirectUri !== null) {
              this.router.navigateByUrl(redirectUri);
            }
          }
          this.isLoggedIn = true;
};

This is my spec file that I am trying:

describe('AppComponent', () => {
  beforeEach(() => {
    let subscription: Subscription = new Subscription();
    TestBed.configureTestingModule({
      imports: [RouterTestingModule],
      declarations: [AppComponent],
      providers: [WindowService, RoleService, HttpClient, HttpHandler, BroadcastService, MsalService,
        {
          provide: MSAL_CONFIG,  // MsalService needs config, this provides it.
          useFactory: () => ({   // Note this is an arrow fn that returns the config object
            redirectUri: window.location.origin + '/',
            clientID: mockData.clientID,
          }),
        }],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    }).compileComponents();
  });

  describe(':', () => {
    function setup() {
      const fixture = TestBed.createComponent(AppComponent);
      const app = fixture.debugElement.componentInstance;
      const compiled = fixture.debugElement.nativeElement;
      return {fixture, app, compiled};
    }

    it('Init with QA environment', () => {
      const {app} = setup();
      spyOn(app.authService, 'getUser').and.returnValue(mockData.userDetails);
      spyOn(app.authService, 'acquireTokenSilent').and.returnValue('msal:acquireTokenSuccess');
      app.ngOnInit();
      app.subscription.add(
        app.broadcastService.subscribe('msal:acquireTokenSuccess', () => {
        // do something here
        });
    );

This is not a direct answer to your question, but more of an indirect one. It might not be that great of an idea to have all this business logic in your component. I would recommend using a library to assist with all your state management (we use NgRX ). A lot of this logic can then be moved into services or other pure methods that will be much easier to test.

We should be creating mocks of our services whenever we create new angular service. Example Code:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

export interface IUserService {
  getUser(): Observable<User>;
}

@Injectable({
  providedIn: 'root'
})
export class UserService implements IUserService {

  constructor(private httpClient: HttpClient) {}
  getUser(): Observable<User> {
    return this.httpClient.get<User>('http://localhost:5000/api');
  }

}

@Injectable()
export class MockUserService implements IUserService {
  getUser(): Observable<User> {
    const user: User =  ...;
    return of(user);
  }

}

After creating mocks of services, in your actual component, assume you know nothing about what's going on in your component. What you should know is that you will be logged in on initialization(that's what I interpreted from your example code).

import { CommonModule } from "@angular/common";
import { HttpClientTestingModule } from "@angular/common/http/testing";
import { TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { RouterTestingModule } from "@angular/router/testing";
import { MsalModule } from "@azure/msal-angular";
import { AppComponent } from "./app.component";
import { RoleService } from "./core/role.service";

describe("AppComponent", () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule,
        CommonModule,
        MsalModule.forRoot({
          clientID: "..."
        })
      ],
      declarations: [AppComponent],
      providers: [
        {
          provide: UserService,
          useClass: MockUserService
        },
        {
          provide: RoleService,
          useClass: MockRoleService
        }
      ]
    }).compileComponents();
  });

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

  it("Init with QA environment", async () => {
    const fixture = TestBed.createComponent(AppComponent);

    // Don't use fixture.componentInstance in this method. You tests should be 
    // written in such a way that tests shouldn't fail when component code is 
    // refactored(method/property names are changed).
    const app = fixture.componentInstance;

    // Execute ngOnInit and other startup code
    fixture.detectChanges();
    // Wait for ngOnInit(and its subscriptions) to complete
    await fixture.whenStable();

    // check if particular UI is available in HTML after successful login
    /**
     * <div *ngIf="loggedIn">
     * <span class="hello">Hello</span>
     * </div>
     */
    const helloElement = fixture.debugElement.query(By.css(".hello"));
    expect(helloElement).toBeTruthy();
  });

 it("Role should be Data Steward", async () => {
    const fixture = TestBed.createComponent(AppComponent);

    // Angular 9 has inject, in previous version, use TestBed.get(UserService)
    const userService = TestBed.inject(UserService);

    // Execute ngOnInit and other startup code
    fixture.detectChanges();
    // Wait for ngOnInit(and its subscriptions) to complete
    await fixture.whenStable();

    expect(userService.userDetails.role).toBe('Data Steward');
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM