【问题标题】:Angular - How to properly test actions that are performed in Observable subscription block?Angular - 如何正确测试在 Observable 订阅块中执行的操作?
【发布时间】:2019-03-08 20:17:10
【问题描述】:

我必须为 Angular 5 应用程序编写单元测试。为了做到这一点,我使用jasmine + jest(我们在公司使用 jest 而不是 karma 来测试速度)。

为了测试我的组件行为(参见下面的代码),我创建了一个订阅相同Observable 的测试作为被测组件,然后等待 2 秒,希望组件的订阅代码块有足够的时间完成,然后,查找组件的内部更改。

问题是随着测试次数的增加,完成测试所需的时间也会增加。而且我个人认为下面一定有更好的方法来测试这种类型的代码。

  • 您将如何处理这种情况?我尝试查看async,但找不到满足我需求的方法。
  • 如何确保我的测试在组件的订阅块之后运行?
  • 如何避免等待 2 秒才能开始测试,而只需等待组件的订阅块完成?

提前感谢您的帮助。


  • 组件:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { SomeService } from './some.service';

@Component({
  selector: 'app-dummy',
  templateUrl: './dummy.component.html',
  styleUrls: ['./dummy.component.scss']
})
export class DummyComponent implements OnInit, OnDestroy {
  isEditable: Boolean;
  //...
  private aSubscriber;

  constructor(private someService: SomeService) {
    this.aSubscriber = someService.anObservable$.subscribe(value => {
      this.isEditable = value;
    });
  }

  ngOnInit() { }

  ngOnDestroy() {
    this.aSubscriber.unsubscribe();
  }
}
  • 服务:

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

@Injectable()
export class SomeService {
  private aSubject = new Subject<any>();
  
  anObservable$ = this.aSubject.asObservable();
  
  constructor() { }

  notify(value) {
    this.aSubject.next(value);
  }
}
  • 规格文件:

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

import { DummyComponent } from './dummy.component';
import { SomeService } from './some.service';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [DummyComponent],
      providers: [SomeService]
    }).compileComponents();
  }));

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

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

  it('should subscribe to anObservable and set values according to the received one',
    inject([SomeService], (service: SomeService) => {
      service.anObservable$.subscribe(value => {
        setTimeout(() => { }, 2000);
        //Test that values are correctly set in the component under test.
        expect(component.isEditable).toBeTruthy();
        //...
      });

      service.notify(true);
  }));
});

【问题讨论】:

    标签: angular typescript asynchronous testing jasmine


    【解决方案1】:

    我经常发现我的代码可以在一定程度上进行重构以适应测试,而重构通常会使代码的耦合度降低、更灵活且更易于阅读。

    在这种情况下,我建议您使用您当前在订阅的“下一个”处理程序中拥有的代码,并将其提取到一个单独的方法中。

    举个例子

      constructor(private someService: SomeService) {
        this.aSubscriber = someService.anObservable$.subscribe(value => {
          this.isEditable = value;
        });
      }
    

    并将其重构为:

      constructor(private someService: SomeService) {
        this.aSubscriber = someService.anObservable$.subscribe(value => {
          this.onValue(value);
        });
      }
    
      private onValue(value) {
        this.isEditable = value;
      }
    

    然后,您可以直接测试“onValue”方法,而无需测试 Observable。我认为,除非您对 Observable 进行更高级的操作(将其连接到 map()、filter() 等),否则您不需要测试 Observable,并且删除了测试的异步方面。

    如果您确实有一个高级管道,您可能应该单独对其进行测试。 像这样拆分它可以分别测试这两种情况。

    【讨论】:

    • 感谢您的回答。我已经喜欢它的简单了。如果我在测试过程中遇到任何问题,我会试一试,然后再将其标记为已批准的答案。谢谢:)
    • 可能您无法测试“onValue”方法,因为它是私有的。一个更好的方法是,重构 SomeService 一种通过方法调用返回“anObservable”的方式。在测试中,使用 Jasmine spyOn 模拟该方法。如果您需要更多详细信息,请告诉我。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    • 1970-01-01
    • 1970-01-01
    • 2020-08-28
    相关资源
    最近更新 更多