如何测试订阅内部方法
How to test subscription inside method
考虑这个 angular 组件:
export class CheckoutComponent {
constructor(private paymentService: PaymentService, private paymentModalService: PaymentModalService) {}
public onCheckout(): void {
const paymentStatus$: Observable<string> = this.paymentService.getStatus();
const paymentRequired$ = paymentStatus$.pipe(map(paymentStatus => paymentStatus === 'INCOMPLETE'));
paymentRequired$.subscribe(() => {
this.paymentModalService.open();
});
}
}
我正在尝试编写 jasmine 测试以确认 paymentModalService.open()
被调用:
import { cold } from 'jasmine-marbles';
describe('CheckoutComponent', () => {
let component: CheckoutComponent;
let mockPaymentService: jasmine.SpyObj<PaymentService>;
let mockPaymentModalService: jasmine.SpyObj<PaymentModalService>;
beforeEach(() => {
mockPaymentService = jasmine.createSpyObj(['getStatus']);
mockPaymentModalService = jasmine.createSpyObj(['open']);
component = new CheckoutComponent(mockPaymentService, mockPaymentModalService);
});
it('should open payment modal if required', () => {
mockPaymentService.getStatus.and.returnValue(cold('a', { a: 'INCOMPLETE' }));
component.onCheckout();
expect(mockPaymentModalService.open).toHaveBeenCalled(); // FAIL: was never called
});
});
但是显然从未调用过 open 方法。
在断言该方法已被调用之前,有什么方法可以等待冷测试 observable 完成发出吗?
我试过使用 fakeAsync
和 tick
,但似乎都没有用。我意识到它适用于 of()
而不是 TestColdObservable
,但我希望对可观察行为进行更精细的控制。我真的不打算完成 getStatus()
observable。
您的主要问题是:
I would like more granular control of observable behavior. I don't really intend to complete the getStatus() observable.
如果你不想完成它那么它本质上不是冷观察,它是热观察,这意味着你可以创建一个主题并通过
var sub = new Subject();
mockPaymentService.getStatus.and.returnValue(sub.asObservable());
// run method and let it subscribe
component.onCheckout();
// fire it
sub.next('a', { a: 'INCOMPLETE' });
expect(mockPaymentModalService.open).toHaveBeenCalled();
我建议您阅读有关如何编写组件测试用例的文档:
Angular guide to writing test cases
问题:
在您当前的测试方法中,您将无法 运行 组件生命周期,而是 运行 手动安装它们并跳过实际测试用例。
它适用于服务,但不适用于组件。
让我给你一些建议,这不是你问题的重点,但无论如何都会有所帮助。
我的建议是将逻辑从组件移至服务。
查看您的代码,CheckoutComponent
似乎需要一个可观察对象 paymentRequired$
,它会在需要付款时发出通知。
我们假设 this.paymentService.getStatus()
是对后端 API 的调用,returns 是付款状态。在这种情况下,我将在服务中做的事情是这样的
export class PaymentService {
constructor(http, ...)
// Private Subjects - using Subject to notify allows multicasting
private _paymentRequired$ = new Subject<boolean>();
// Public API Streams
public paymentRequired$ = this._paymentRequired$.asObservable();
// Public API methods
public getStatus() {
this.http.get(....).pipe(
tap({
next: resp => this._paymentRequired$.next(resp === 'INCOMPLETE'),
error: err => {// manage the error case}
})
)
}
}
如果你像这样将逻辑移动到服务中,那么 CheckoutComponent
可以简单地在其 ngOnInit
方法中订阅 paymentService.paymentRequired$
observable,而测试只能处理使用服务而不是组件。
特别是测试必须以某种方式实例化服务,订阅 paymentRequired$
可观察对象,调用 getStatus()
并检查 paymentRequired$
是否已通知。
阅读有关 component marble tests 的文档,我发现我需要像这样刷新测试调度程序:
import { cold, getTestScheduler } from 'jasmine-marbles';
it('should open payment modal if required', () => {
mockPaymentService.getStatus.and.returnValue(cold('a', { a: 'INCOMPLETE' }));
component.onCheckout();
getTestScheduler().flush(); // <=== prompt the scheduler to execute all of its queued actions
expect(mockPaymentModalService.open).toHaveBeenCalled(); // SUCCESS
});
考虑这个 angular 组件:
export class CheckoutComponent {
constructor(private paymentService: PaymentService, private paymentModalService: PaymentModalService) {}
public onCheckout(): void {
const paymentStatus$: Observable<string> = this.paymentService.getStatus();
const paymentRequired$ = paymentStatus$.pipe(map(paymentStatus => paymentStatus === 'INCOMPLETE'));
paymentRequired$.subscribe(() => {
this.paymentModalService.open();
});
}
}
我正在尝试编写 jasmine 测试以确认 paymentModalService.open()
被调用:
import { cold } from 'jasmine-marbles';
describe('CheckoutComponent', () => {
let component: CheckoutComponent;
let mockPaymentService: jasmine.SpyObj<PaymentService>;
let mockPaymentModalService: jasmine.SpyObj<PaymentModalService>;
beforeEach(() => {
mockPaymentService = jasmine.createSpyObj(['getStatus']);
mockPaymentModalService = jasmine.createSpyObj(['open']);
component = new CheckoutComponent(mockPaymentService, mockPaymentModalService);
});
it('should open payment modal if required', () => {
mockPaymentService.getStatus.and.returnValue(cold('a', { a: 'INCOMPLETE' }));
component.onCheckout();
expect(mockPaymentModalService.open).toHaveBeenCalled(); // FAIL: was never called
});
});
但是显然从未调用过 open 方法。
在断言该方法已被调用之前,有什么方法可以等待冷测试 observable 完成发出吗?
我试过使用 fakeAsync
和 tick
,但似乎都没有用。我意识到它适用于 of()
而不是 TestColdObservable
,但我希望对可观察行为进行更精细的控制。我真的不打算完成 getStatus()
observable。
您的主要问题是:
I would like more granular control of observable behavior. I don't really intend to complete the getStatus() observable.
如果你不想完成它那么它本质上不是冷观察,它是热观察,这意味着你可以创建一个主题并通过
var sub = new Subject();
mockPaymentService.getStatus.and.returnValue(sub.asObservable());
// run method and let it subscribe
component.onCheckout();
// fire it
sub.next('a', { a: 'INCOMPLETE' });
expect(mockPaymentModalService.open).toHaveBeenCalled();
我建议您阅读有关如何编写组件测试用例的文档: Angular guide to writing test cases
问题: 在您当前的测试方法中,您将无法 运行 组件生命周期,而是 运行 手动安装它们并跳过实际测试用例。 它适用于服务,但不适用于组件。
让我给你一些建议,这不是你问题的重点,但无论如何都会有所帮助。
我的建议是将逻辑从组件移至服务。
查看您的代码,CheckoutComponent
似乎需要一个可观察对象 paymentRequired$
,它会在需要付款时发出通知。
我们假设 this.paymentService.getStatus()
是对后端 API 的调用,returns 是付款状态。在这种情况下,我将在服务中做的事情是这样的
export class PaymentService {
constructor(http, ...)
// Private Subjects - using Subject to notify allows multicasting
private _paymentRequired$ = new Subject<boolean>();
// Public API Streams
public paymentRequired$ = this._paymentRequired$.asObservable();
// Public API methods
public getStatus() {
this.http.get(....).pipe(
tap({
next: resp => this._paymentRequired$.next(resp === 'INCOMPLETE'),
error: err => {// manage the error case}
})
)
}
}
如果你像这样将逻辑移动到服务中,那么 CheckoutComponent
可以简单地在其 ngOnInit
方法中订阅 paymentService.paymentRequired$
observable,而测试只能处理使用服务而不是组件。
特别是测试必须以某种方式实例化服务,订阅 paymentRequired$
可观察对象,调用 getStatus()
并检查 paymentRequired$
是否已通知。
阅读有关 component marble tests 的文档,我发现我需要像这样刷新测试调度程序:
import { cold, getTestScheduler } from 'jasmine-marbles';
it('should open payment modal if required', () => {
mockPaymentService.getStatus.and.returnValue(cold('a', { a: 'INCOMPLETE' }));
component.onCheckout();
getTestScheduler().flush(); // <=== prompt the scheduler to execute all of its queued actions
expect(mockPaymentModalService.open).toHaveBeenCalled(); // SUCCESS
});