Home > Back-end >  Unit test for method with observables and promise
Unit test for method with observables and promise

Time:05-31

I have this method that I am trying to write a unit test for. The test currently is just going to spyOn methods being called in the correct order.

      handleSave() {
        this.saving = true;
        this.presenters.presentLoader({
          message: "Uploading..."
        });
        this.loginService.loginToFirebase().subscribe(() => {
          Promise.all(
            this.credential.inputs.map((input) => this.putStorageItem(input))
          )
          .then((url) => {
            this.presenters.dismissLoader();
            this.presenters.presentAlert({
              message: 'Successfully Uploaded Identification.',
              buttons: ['Ok']
            });
            this.presenters.dismissModal(true);
            this.saving = false
          })
          .catch((error) => {
            this.presenters.dismissLoader();
            this.presenters.presentAlert({
              message: 'Something went wrong.'
            });
            this.saving = false
          });
        })
      }

The Unit test so far works for the initial call of presentLoader but the subsequent dismissLoader (which is inside the loginToFirebase observable and the Promise.all doesn't get called (at least not before the observable and promises resolve)

In my before each I mock the putStorageItem component.putStorageItem = (item) => Promise.resolve(true); In and I mock the loginService with this.

    {
      provide: LoginService,
      useValue: {
        loginToFirebase: () => of(true)
      }
    },

My current test

    it("call handle save should do stuff", () => {
        let presenters = TestBed.inject(PresentersService);
    
        spyOn(presenters, 'presentLoader');
        spyOn(presenters, 'dismissLoader');
        spyOn(presenters, 'presentAlert');
        spyOn(presenters, 'dismissModal');
    
        component.handleSave();
    
        expect(presenters.presentLoader).toHaveBeenCalled();
        expect(presenters.dismissLoader).toHaveBeenCalled();
        expect(presenters.presentAlert).toHaveBeenCalled();
        expect(presenters.dismissModal).toHaveBeenCalled();
    
      });

putStorageItem

      putStorageItem(input) {
        const fileExt = input.file.metadata.name.split('.').slice(-1);
        const filepath = ${input.label}.${fileExt}`;
        let task;
        if(input.file.metadata.isBase64) {
          task = this.fireStorage.ref(filepath).put(this.util.b64toBlob(input.file.img), {contentType:input.file.metadata.type, customMetadata:{originalName:input.file.metadata.name}});
        } else {
          task = this.fireStorage.ref(filepath).put(input.file.img, {contentType:input.file.metadata.type, customMetadata:{originalName:input.file.metadata.name}});
        }
        input.fileProgress = task.percentageChanges();
        return task.then((snapshot) => {
          console.log('One success:', input.file)
        }).catch((error) => {
          console.log('One failed:', input.file, error.message)
        });
      }

CodePudding user response:

Since you're using Observables and/or promises, you need to use different techniques for testing. The techniques are:

  • the done callback of Jasmine
  • async/await and fixture.whenStable() of Angular
  • waitForAsync and fixture.whenStable() of Angular
  • fakeAsync and tick of Angular

You can read more about asynchronous testing in Angular as well.

My favorite is fakeAsync and tick and I think it can help you in this scenario.

// !! wrap the test in a `fakeAsync` so you have better control of promises
it("call handle save should do stuff", fakeAsync(() => {
        let presenters = TestBed.inject(PresentersService);
    
        spyOn(presenters, 'presentLoader');
        spyOn(presenters, 'dismissLoader');
        spyOn(presenters, 'presentAlert');
        spyOn(presenters, 'dismissModal');
    
        component.handleSave();
        // !! Call tick() to tell the test that before running the
        // statements below the tick, 
        // ensure the promises in the component code have resolved
        // since the expect statements rely on them.
        tick();
    
        expect(presenters.presentLoader).toHaveBeenCalled();
        expect(presenters.dismissLoader).toHaveBeenCalled();
        expect(presenters.presentAlert).toHaveBeenCalled();
        expect(presenters.dismissModal).toHaveBeenCalled();
    
      }));

The tick can also be used to ensure the subscribe of an observable stream has completed before continuing but I mainly use it for promises.

  • Related