如何在 Angular Jasmine 规范中测试一个主题和可观察的 return 值?

How to test a subject and observable return value in Angular Jasmine Spec?

在 Angular9 中,我有一个如下所示的加载器服务。

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

export interface LoaderState {
  show: boolean;
}

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

  private loaderSubject = new Subject<LoaderState>();

  loaderState = this.loaderSubject.asObservable();

  constructor() { }

  show() {
      this.loaderSubject.next({show: true} as LoaderState);
  }

  hide() {
      this.loaderSubject.next({show: false} as LoaderState);
  }
}

我想测试 show()hide() 方法。为此,我编写了如下规范。

it('should show', (done) => {
      spyOn(loaderService, 'show').and.callThrough();
      loaderService.show();
      expect(loaderService.show).toHaveBeenCalled();
      loaderService.loaderState.subscribe((state) => {
          expect(state).toBe({show: true});
          done();
      });
  });

但是当我 运行 这个

Chrome 86.0.4240 (Windows 10.0.0) LoaderService should show FAILED Error: Timeout - Async callback was not invoked within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)

问这个问题之前查了很多资料。但似乎无法找到合适的解决方案。我是 Jasmin 单元测试的新手。感谢任何帮助。

编辑:发布完整的规范文件以供参考。

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { LoaderComponent } from './loader.component';
import { LoaderService } from './loader.service';

describe('LoaderService', () => {
  let component: LoaderComponent;
  let fixture: ComponentFixture<LoaderComponent>;
  let loaderService: LoaderService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoaderComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoaderComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    loaderService = TestBed.inject(LoaderService);
  });

  it('should be create', () => {
    expect(loaderService).toBeTruthy();
  });

  it('should show', (done) => {
      spyOn(loaderService, 'show').and.callThrough();
      loaderService.show();
      expect(loaderService.show).toHaveBeenCalled();
      loaderService.loaderState.subscribe((state) => {
          expect(state).toBe({show: true});
          done();
      });
  });
});

问题是您在实际方法调用之后调用订阅。因此,当您调用 show(); 时,没有任何内容订阅该事件,并且由于 done() 回调在其中......它从未被调用过。

it('should show', (done) => {
    service.loaderState.subscribe((state) => { //subscribe first
      expect(state).toEqual({show: true}); // change toEqual instead of toBe since you're comparing objects
      done();
      });

    spyOn(service, 'show').and.callThrough();
    service.show(); // invoke after
    expect(service.show).toHaveBeenCalled();
  });

这是我对此模式的解决方案:

服务Class

import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable({

  providedIn: 'root'

})

export class MyService {

  private myServiceEvent = new Subject<any>();

  constructor() { }

  success(): Observable<any> {
    return this.myServiceEvent.asObservable();
  }
  
  doSomething(someData: any): void {
    // stuff
    this.myServiceEvent.next(someData);
  }
}

使用该服务的组件(可观察对象将由调用该服务上的 doSomething 方法的其他组件或服务触发)

import { Component, OnInit } from '@angular/core';
import { MyService } from ./my-service.ts

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

    private test = '';

  constructor(
    private myService: MyService,
  ) {  }

  ngOnInit() {
    this.myService.success().subscribe(data => {
        test = data;
    });
  }
}

使用服务的 Observable 响应测试组件的规范。请注意服务模拟的构造以及 Subject 变量如何用于触发 Observable。

import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Observable, Subject } from 'rxjs';

import { AppMyComponent } from './app-my.component';
import { MyService } from ./my-service.ts

describe('AppMyComponent', () => {
  let component: AppMyComponent;
  let fixture: ComponentFixture<AppMyComponent>;

  const myServiceSubject = new Subject<any>();

  const myServiceMock = jasmine.createSpyObj('MyService', [], {
    success: () => myServiceSubject.asObservable()
  });
  
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      schemas: [ CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
      declarations: [ AppMyComponent ],
      providers: [
        { provide: MyService, useValue: myServiceMock }
      ]
    })
    .compileComponents();
  }));

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

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should execute subscription to MyService success event', fakeAsync(() => {
    myServiceSubject.next('somedata');
    tick();
    expect(componenet.test).toEqual('somedata');
  }));