首页 > 解决方案 > Jasmine,如何测试可观察的方法逻辑?

问题描述

什么是测试发出新值this.onClickSaved()时调用 switch/case 块中的方法多少次的正确解决方案?onMessageActionTriggered$

// 服务

export class WebMessageService {
  private _onMessageActionTriggered = new BehaviorSubject<ITriggerMessageAction>(undefined);
  get onMessageActionTriggered$(): Observable<ITriggerMessageAction> {
    return this._onMessageActionTriggered.asObservable();
  }

  triggerMessageAction(actionDto: ITriggerMessageAction) {
    this._onMessageActionTriggered.next(actionDto);
  }          
}

// 零件

export class MessageComponent{
  ngOnInit() {
    this._subscribeToActionsFromConversationHeader();
  }

  private _subscribeToActionsFromConversationHeader() {
    this.subscriptions.push(
      this._messageService.onMessageActionTriggered$
        .pipe(filter(dto => dto?.messageId === this.model.id && dto?.streamId === this.stream.streamId))
        .subscribe(dto => {
          switch (dto.newState) {
            case WebcareState.Saved: {
              this.onClickSaved();
              break;
            }
            case WebcareState.Completed: {
              this.onClickCompleted();
              break;
            }
            case WebcareState.MarkedAsSpam: {
              this.onClickMarkedAsSpam();
              break;
            }
          }
        })
    );
  }
}

标签: angulartypescriptrxjsjasmineangular-test

解决方案


您应该存根onClickSavedonClickCompletedonClickMarkedAsSpam方法,以便它们没有副作用。这样,我们就可以_subscribeToActionsFromConversationHeader在一个孤立的环境中测试该方法。messagService.triggerMessageAction()您应该在通过调用方法发出值之前对它们进行存根。

例如使用angularv11+:

message.component.ts

import { Component } from '@angular/core';
import { filter } from 'rxjs/operators';
import { WebcareState, WebMessageService } from './message.service';

@Component({
  selector: 'app-message',
  template: '<span>message component</span>',
})
export class MessageComponent {
  subscriptions = [];
  model = { id: '1' };
  stream = { streamId: '1' };

  constructor(private _messageService: WebMessageService) {}

  ngOnInit() {
    this._subscribeToActionsFromConversationHeader();
  }

  onClickSaved() {}
  onClickCompleted() {}
  onClickMarkedAsSpam() {}

  private _subscribeToActionsFromConversationHeader() {
    this.subscriptions.push(
      this._messageService.onMessageActionTriggered$
        .pipe(
          filter(
            (dto) =>
              dto?.messageId === this.model.id &&
              dto?.streamId === this.stream.streamId
          )
        )
        .subscribe((dto) => {
          switch (dto.newState) {
            case WebcareState.Saved: {
              this.onClickSaved();
              break;
            }
            case WebcareState.Completed: {
              this.onClickCompleted();
              break;
            }
            case WebcareState.MarkedAsSpam: {
              this.onClickMarkedAsSpam();
              break;
            }
          }
        })
    );
  }
}

message.service.ts

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

export enum WebcareState {
  Saved = 'Saved',
  Completed = 'Completed',
  MarkedAsSpam = 'MarkedAsSpam',
}

export interface ITriggerMessageAction {
  messageId?: string;
  streamId?: string;
  newState: WebcareState;
}

@Injectable()
export class WebMessageService {
  private _onMessageActionTriggered = new BehaviorSubject<ITriggerMessageAction>(
    undefined
  );
  get onMessageActionTriggered$(): Observable<ITriggerMessageAction> {
    return this._onMessageActionTriggered.asObservable();
  }

  triggerMessageAction(actionDto: ITriggerMessageAction) {
    this._onMessageActionTriggered.next(actionDto);
  }
}

message.component.spec.ts

import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MessageComponent } from './message.component';
import {
  ITriggerMessageAction,
  WebcareState,
  WebMessageService,
} from './message.service';

fdescribe('65291642', () => {
  let fixture: ComponentFixture<MessageComponent>;
  let component: MessageComponent;
  let messageService: WebMessageService;
  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [MessageComponent],
        providers: [WebMessageService],
      })
        .compileComponents()
        .then(() => {
          fixture = TestBed.createComponent(MessageComponent);
          component = fixture.componentInstance;
          messageService = TestBed.inject(WebMessageService);
        });
    })
  );

  it('should handle click saved', () => {
    const testAction: ITriggerMessageAction = {
      messageId: '1',
      streamId: '1',
      newState: WebcareState.Saved,
    };

    const onClickSavedSpy = spyOn(component, 'onClickSaved').and.stub();
    messageService.triggerMessageAction(testAction);
    fixture.detectChanges();
    expect(onClickSavedSpy).toHaveBeenCalled();
    expect(component.subscriptions).toHaveSize(1);
  });

  it('should handle click completed', () => {
    const testAction: ITriggerMessageAction = {
      messageId: '1',
      streamId: '1',
      newState: WebcareState.Completed,
    };

    const onClickCompletedSpy = spyOn(component, 'onClickCompleted').and.stub();
    messageService.triggerMessageAction(testAction);
    fixture.detectChanges();
    expect(onClickCompletedSpy).toHaveBeenCalled();
    expect(component.subscriptions).toHaveSize(1);
  });

  it('should handle click completed', () => {
    const testAction: ITriggerMessageAction = {
      messageId: '1',
      streamId: '1',
      newState: WebcareState.MarkedAsSpam,
    };

    const onClickMarkedAsSpamSpy = spyOn(
      component,
      'onClickMarkedAsSpam'
    ).and.stub();
    messageService.triggerMessageAction(testAction);
    fixture.detectChanges();
    expect(onClickMarkedAsSpamSpy).toHaveBeenCalled();
    expect(component.subscriptions).toHaveSize(1);
  });
});

单元测试结果:

在此处输入图像描述


推荐阅读