I would like to find a way to test unsubscribe function calls on Subscriptions and Subjects.
I came up with a few possible solutions, but every one of these have pros and cons. Please keep in mind that I do not want to alter the access modifier of a variable for testing purposes.
In that case I have a private class variable which stores a subscription:
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);
pros:
cons:
component.ts: same as in option 1
component.spec.ts:
spyOn(Subscription.prototype, 'unsubscribe'); component.ngOnDestroy(); expect(Subscription.prototype.unsubscribe).toHaveBeenCalledTimes(1);
pros:
cons:
subscription.helper.ts:
export class SubscriptionHelper { static unsubscribeAll(...subscriptions: Subscription[]) { subscriptions.forEach((subscription: Subscription) => { subscription.unsubscribe(); }); } }
component.ts: same as in option 1, but ngOnDestroy is different:
ngOnDestroy(): void { SubscriptionHelper.unsubscribeAll(this.mySubscription); }
component.spec.ts:
spyOn(SubscriptionHelper, 'unsubscribeAll'); component.ngOnDestroy(); expect(SubscriptionHelper.unsubscribeAll).toHaveBeenCalledTimes(1);
pros:
cons:
What do you guys suggest? How do you test the cleanup in unit test?
They all have complete() method, so if you call that method, even if you had 100 subscriptions, they will all unsubscribed automatically.
🎩 Automagically Unsubscribe in Angular As you probably know when you subscribe to an observable or event in JavaScript, you usually need to unsubscribe at a certain point to release memory in the system. Otherwise, you will have a memory leak. A memory leak occurs when a section of memory that is no longer being…
I had exactly the same problem, here's my solution:
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(); });
This works for me, I hope it will for you too !
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With