Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test unsubscribe function in angular

Tags:

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.

  1. Accessing private variable of component with reflection.

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:

  • I can reach mySubscription.
  • I can test that the unsubscribe method was invoked on the right subscription.

cons:

  • I can reach mySubscription only with reflection, what I would like to avoid if possible.

  1. I create a variable for subscription just like in option 1., but instead of reaching the variable with reflection I simply check that the unsubscribe method was invoked, without knowing the source.

component.ts: same as in option 1


component.spec.ts:

spyOn(Subscription.prototype, 'unsubscribe'); component.ngOnDestroy(); expect(Subscription.prototype.unsubscribe).toHaveBeenCalledTimes(1); 

pros:

  • I can test that the unsubscribe method was called

cons:

  • I can not test the source of the invoked unsubscribe method.

  1. I implemented a helper function which invokes unsubscribe method on the passed parameters which are subscriptions.

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:

  • I can test that the helper function was called

cons:

  • I can not test that the unsubscribe function was called on a specific subscription.

What do you guys suggest? How do you test the cleanup in unit test?

like image 878
gr4viton Avatar asked Sep 21 '18 10:09

gr4viton


People also ask

Does Angular unsubscribe automatically?

They all have complete() method, so if you call that method, even if you had 100 subscriptions, they will all unsubscribed automatically.

Do we need to unsubscribe subject in Angular?

🎩 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…


1 Answers

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 !

like image 85
ethanfar Avatar answered Sep 23 '22 01:09

ethanfar