我想找到一种方法来测试对“订阅和主题”的取消订阅功能的调用。
我想出了一些可能的解决方案,但是每个解决方案都有其优缺点。请记住,我不想出于测试目的而更改变量的访问修饰符。
在这种情况下,我有一个存储预订的私有类变量:
component.ts:
private mySubscription: Subscription;
//...
ngOnInit(): void {
this.mySubscription = this.store
.select(mySelector)
.subscribe((value: any) => console.log(value));
}
ngOnDestroy(): void {
this.mySubscription.unsubscribe();
}
component.spec.ts:
spyOn(component['mySubscription'], 'unsubscribe');
component.ngOnDestroy();
expect(component['mySubscription'].unsubscribe).toHaveBeenCalledTimes(1);
优点:
缺点:
component.ts:与选项1相同
component.spec.ts:
spyOn(Subscription.prototype, 'unsubscribe');
component.ngOnDestroy();
expect(Subscription.prototype.unsubscribe).toHaveBeenCalledTimes(1);
优点:
缺点:
subscription.helper.ts:
export class SubscriptionHelper {
static unsubscribeAll(...subscriptions: Subscription[]) {
subscriptions.forEach((subscription: Subscription) => {
subscription.unsubscribe();
});
}
}
component.ts:与选项1相同,但是ngOnDestroy不同:
ngOnDestroy(): void {
SubscriptionHelper.unsubscribeAll(this.mySubscription);
}
component.spec.ts:
spyOn(SubscriptionHelper, 'unsubscribeAll');
component.ngOnDestroy();
expect(SubscriptionHelper.unsubscribeAll).toHaveBeenCalledTimes(1);
优点:
缺点:
你们有什么建议?您如何在单元测试中测试清理?
答案 0 :(得分:2)
我想出一种使之工作的方法。
首先,我不会为每个订阅创建一个订阅实例,我通常只使用一个订阅实例,并通过使用实现撕裂逻辑并允许子订阅的方法“ add”将所需的订阅添加到所需的多个订阅中。
private subscriptions: Subscription;
//...
ngOnInit(): void {
this.mySubscription.add(
this.store
.select(mySelector)
.subscribe((value: any) => console.log(value))
);
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
这样做,在我的测试中,我可以依靠Subscriptions原型并检测 取消订阅方法调用。在这种情况下,我打电话给ngOnDestroy。
const spy = spyOn(Subscription.prototype, 'unsubscribe');
component.ngOnDestroy();
expect(spy).toHaveBeenCalledTimes(1);
答案 1 :(得分:1)
我遇到了完全相同的问题,这是我的解决方案:
component.ts:
private subscription: Subscription;
//...
ngOnInit(): void {
this.subscription = this.route.paramMap.subscribe((paramMap: ParamMap) => {
// ...
});
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
component.spec.ts:
let dataMock;
let storeMock: Store;
let storeStub: {
select: Function,
dispatch: Function
};
let paramMapMock: ParamMap;
let paramMapSubscription: Subscription;
let paramMapObservable: Observable<ParamMap>;
let activatedRouteMock: ActivatedRoute;
let activatedRouteStub: {
paramMap: Observable<ParamMap>;
};
beforeEach(async(() => {
dataMock = { /* some test data */ };
storeStub = {
select: (fn: Function) => of((id: string) => dataMock),
dispatch: jasmine.createSpy('dispatch')
};
paramMapMock = {
keys: [],
has: jasmine.createSpy('has'),
get: jasmine.createSpy('get'),
getAll: jasmine.createSpy('getAll')
};
paramMapSubscription = new Subscription();
paramMapObservable = new Observable<ParamMap>();
spyOn(paramMapSubscription, 'unsubscribe').and.callThrough();
spyOn(paramMapObservable, 'subscribe').and.callFake((fn: Function): Subscription => {
fn(paramMapMock);
return paramMapSubscription;
});
activatedRouteStub = {
paramMap: paramMapObservable
};
TestBed.configureTestingModule({
// ...
providers: [
{ provide: Store, useValue: storeStub },
{ provide: ActivatedRoute, useValue: activatedRouteStub }
]
})
.compileComponents();
}));
// ...
it('unsubscribes when destoryed', () => {
fixture.detectChanges();
component.ngOnDestroy();
expect(paramMapSubscription.unsubscribe).toHaveBeenCalled();
});
这对我有用,我希望对您也有用!
答案 2 :(得分:0)
关于最后一点
我无法测试是否在特定订阅上调用了取消订阅功能。
您可以实际做到。
// get your subscrption
const mySubscription = component['mySubscription'];
// use the reference to confirm that it was called on that specific subscription only.
expect(SubscriptionHelper.unsubscribeAll).toHaveBeenCalledWith(mySubscription);
现在,在进行测试时,它应该只检查/访问/致电公众成员,并测试/预期其效果。
使用豪华的javascript,您期望/验证私有成员,即使这样也很好,我认为。即使我这样做,我可能也是错的。欢迎进行生产性批评和评论。
答案 3 :(得分:0)
我放弃了COMMENTS,获得了更多空间,即使我只是一个开放的推理。我希望SO可以接受。
我想测试该组件是否在每个调用取消订阅 订阅
我认为这是测试设计的一个有趣案例。
我们都知道,在组件被销毁时不要使订阅保持有效很重要,因此值得思考如何进行测试。
同时,这可以看作是实施细节,它当然不属于SW组件通过其API公开的“ 可公开访问的行为” 。如果您不希望将测试和代码紧密结合在一起,那么公共API应该成为测试的重点领域,这阻碍了重构。
Plus AFAIK RxJS没有提供在给定时刻列出所有活动订阅的机制,因此寻找一种方法来测试它的任务仍然在我们身上。
因此,如果您要达到测试所有订阅都未订阅的目的,则需要采用特定的设计来允许这样做,例如使用存储所有订阅的数组,然后检查是否已关闭所有订阅。这是您的SubscriptionHelper
所在的行。
但是即使这样也不一定能把您放在安全的地方。您总是可以创建一个新的订阅,而不将其添加到数组中,可能仅仅是因为您忘记在代码中这样做。
因此,只有遵守编码准则(即为数组添加订阅),您才能进行测试。但这等同于确保您使用ngOnDestroy
方法取消订阅所有订阅。甚至这都是编码学科。
那么,即使测试的最终目标很重要,进行这样的测试真的有意义吗?通过代码审查可以达到目的吗?
很好奇阅读观点
答案 4 :(得分:0)
这对您来说好吗?
component.mySubscription = new Subscription();
spyOn(component.mySubscription, 'unsubscribe');
component.ngOnDestroy();
expect(component.mySubscription.unsubscribe).toHaveBeenCalledTimes(1);
答案 5 :(得分:0)
Lucien Dulac和Paritosh几乎正确:
component.ts
:
private subscriptions = new Subscription();
constructor(): void {
this.subscriptions.add(...);
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
component.spec.ts
:
spyOn(component['subscriptions'], 'unsubscribe');
component.ngOnDestroy();
expect(component['subscriptions'].unsubscribe).toHaveBeenCalled();
答案 6 :(得分:0)
最务实的做法是把事情掌握在自己手中,只是使用的语言功能。
const cmp = fixture.componentInstance as any;
cmp.mySubscription = jasmine.createSpyObj<Subscription>(['unsubscribe']);
请记住这是不absolutelly测试私有代码的模式。你仍然需要测试公共件间接地证明专用代码。
答案 7 :(得分:0)
.component.ts
:
...
private someSubscription!: Subscription;
...
ngOnInit(): void {
this.someSubscription = ... .subscribe(() => ...);
}
...
ngOnDestroy(): void {
this.someSubscription.unsubscribe();
}
...
.component.spec.ts
:
...
it('should unsubscribe', () => {
component['someSubscription'] = of().subscribe();
const unsubscriptionSpy = jest.spyOn(component['someSubscription'], 'unsubscribe');
component.ngOnDestroy();
expect(unsubscriptionSpy).toHaveBeenCalled();
});
...
修改ngOnDestroy(组件)内部的代码时,测试失败。因此,这个解决方案对我来说效果很好。
答案 8 :(得分:-1)
对于我个人而言,我不介意使用第一个选项来选择私有mySubscription
属性。
但是,如果您不想访问私有属性,并且在需要自己调用done
函数的地方使用异步测试,则可以将其设为ngOnDestroy()
的可选参数(这也期望您只能在组件生命周期内仅致电ngOnDestroy
一次:
function ngOnDestroy(done?: () => void): void {
this.mySubscription.add(done);
this.mySubscription.unsubscribe();
}
现在调用ngOnDestroy
会先取消订阅mySubscription
,然后再调用done
(如果已设置)。您知道订阅已取消订阅,因为测试通过并且没有超时。
然后在您的测试中,例如:
describe('', () => {
it('', done => {
const component = ...
component.ngOnDestroy(done)
})
})
未经测试的实时演示:https://stackblitz.com/edit/rxjs6-demo-rujesw?file=index.ts