【问题标题】:How to test a subject and observable return value in Angular Jasmine Spec?如何在 Angular Jasmine Spec 中测试主题和可观察的返回值?
【发布时间】:2022-04-01 00:38:58
【问题描述】:

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

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 应该显示 FAILED 错误:超时 - 5000 毫秒内未调用异步回调(由 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();
      });
  });
});

【问题讨论】:

  • 你模拟了你的服务吗?
  • @Fran 我需要模拟吗?服务非常简单。我不能在不嘲笑的情况下进行测试吗?我正在使用测试台在每个之前注入。
  • 抱歉,您是对的,我认为您正在测试不同的服务。 state 在您的订阅中的价值是多少?
  • @Fran show() 函数将值设置为 {show: true} 。我不认为订阅被调用,因此时间问题说异步回调没有被调用。
  • 你能提供整个.spec吗?

标签: angular unit-testing jasmine karma-jasmine


【解决方案1】:

问题是您在实际方法调用之后调用 subscribe。因此,当您调用 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();
  });

【讨论】:

    【解决方案2】:

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

    服务类

    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);
      }
    }
    

    使用服务的组件(observable 将由调用服务上的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');
      }));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-06
      • 2019-06-19
      • 2021-03-25
      • 2019-05-15
      • 2020-02-25
      • 1970-01-01
      • 2016-08-23
      • 2018-06-21
      相关资源
      最近更新 更多