Home > Blockchain >  Testing OnDestroy component observable
Testing OnDestroy component observable

Time:09-19

I have a component, which calls on some observables and listens to them, and cleans the subscriptions (advances and closes them) on destroy, but I can't seem to get the test working properly. Am I running the test wrong? The component does clean up its observables.

Component

export class DevicesComponent implements OnDestroy {
   public completer$: Subject<void> = new Subject<void>();

   this.deviceFacade.dataLoaded$.pipe(takeUntil(this.completer$)).subscribe({
            next: (loading: boolean) => {
                if (!loading) {
                    this.spinner.show();
                } else {
                    this.spinner.hide();
                }
            },
            error: (err: string | null) => console.log(err),
        });

    ngOnDestroy(): void {
        this.completer$.next();
        this.completer$.complete();
    }
}

Test

    it('should call method and unsubscribe on destroy', () => {
        const destroy = spyOn(component, 'ngOnDestroy');
        const next = spyOn(component.completer$, 'next');
        const complete = spyOn(component.completer$, 'complete');

        component.ngOnDestroy();
        fixture.detectChanges();

        expect(destroy).toHaveBeenCalled();
        expect(next).toHaveBeenCalled();
        expect(complete).toHaveBeenCalled();
    });

CodePudding user response:

You are wanting to test the lifecycle hook in angular, and we can do that!

The fixture.destroy() method is what you'll want to call, but first let's talk about your use of spyOn.

When you call spyOn(someObj, 'someProperty') it spys on it and OVERWRITES the function to do nothing. Instead, when you need to spy on a function and still have the function run as normal, you should use spyOn(someObj, 'someProperty).and.callThrough(). As you have it written the ngOnDestroy method won't do anything.

As for your test, what you really want to know is that your subscriptions have been closed. The best approach is to make sure that your dependencies are interacted with as you expect.

it('should call method and unsubscribe on destroy', () => {
  // try to avoid spying on your component - it is what you're testing!
  //const destroy = spyOn(component, 'ngOnDestroy');
  //const next = spyOn(component.completer$, 'next');
  //const complete = spyOn(component.completer$, 'complete');
  
  // you should have a deviceFacade service that is set to a subject or behaviorSubject in the beforeEach
  // fakeDeviceFacade.dataLoaded$ = new BehaviorSubject(false);
  // it won't work if you set fakeDeviceFacade.dataLoaded$ = of(something) 
  expect(fakeDeviceFacade.dataLoaded$.observers.length).toBe(1);
  fixture.destroy();
  expect(fakeDeviceFacade.dataLoaded$.observers.length).toBe(0);
});

CodePudding user response:

spyOn doesn't invoke the actual implementation by default. You can enable it with and.callThrough()

    it('should call method and unsubscribe on destroy', () => {
        const destroy = spyOn(component, 'ngOnDestroy').and.callThrough();
        const next = spyOn(component.completer$, 'next');
        const complete = spyOn(component.completer$, 'complete');

        component.ngOnDestroy();
        fixture.detectChanges();

        expect(destroy).toHaveBeenCalled();
        expect(next).toHaveBeenCalled();
        expect(complete).toHaveBeenCalled();
    });

https://scriptverse.academy/tutorials/jasmine-spyon.html

  • Related