angular - 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;
}
}
})
);
}
}
解决方案
您应该存根onClickSaved
、onClickCompleted
和onClickMarkedAsSpam
方法,以便它们没有副作用。这样,我们就可以_subscribeToActionsFromConversationHeader
在一个孤立的环境中测试该方法。messagService.triggerMessageAction()
您应该在通过调用方法发出值之前对它们进行存根。
例如使用angular
v11+:
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);
});
});
单元测试结果:
推荐阅读
- regex - 正则表达式过滤电子表格中的 URL 列表
- c++ - 即使单词中有括号,如何在括号内选择完整的单词
- postgresql - pgr_createTopology() 函数中的参数作为查询
- angular - 清除“Angular4”中的文本框
- python - 从 IBM Watson Personal Insights - Python 获取更多信息
- xcode - 如何在 Xcode 和 Source Tree 中隐藏错误和警告 bash 颜色?
- dax - dax 两列有 isKey=true
- bash - 在两个单词和特定行中提取文本
- sql-server - (SSRS) Tablix 高级分组、隐藏单元格和对齐单元格
- sql - 如何使用 SQL 在 VBA Access 中更新包含 NULL 值的列?