#Jest test promise observable with retry delay (RxJS)

6 messages · Page 1 of 1 (latest)

zenith tusk
#

I am lost, how do i write the unit test for observables with a retry delay in it and have the observable turned into a promise.

#

code i try to test:

    { afbeeldingen, voertuignr_hexon }: HexonVehicleData,
    queueId: number
  ): Promise<WooCommerceImage[] | undefined> {
    let tempImages = afbeeldingen.afbeelding;
    if (!tempImages) return;

    if (!Array.isArray(tempImages)) tempImages = [tempImages];
    const images = tempImages.map((afbeelding) => afbeelding.url);
    let failedImages = 0;
    let downloadsFailed = 0;

    const imagesObservable = from(images).pipe(
      concatMap((image, index) => {
        return this.httpService
          .get(image, { responseType: 'arraybuffer' })
          .pipe(
            retry({ count: 5, delay: 3000 }),
            map((response) => ({
              buffer: Buffer.from(response.data),
              mimetype: response.headers['content-type'],
              originalname: image.split('/').pop(),
            })),
            tap(() => {}),
            catchError((error) => {
              failedImages++;
              return of(null);
            })
          );
      }),
      filter((file) => file !== null),
      toArray(),
      map((files) =>
        files.sort((a, b) => {
          const numberA = Number(a.originalname.split('-').pop().split('.')[0]);
          const numberB = Number(b.originalname.split('-').pop().split('.')[0]);
          return numberA - numberB;
        })
      ),
      tap(() => {
        downloadsFailed = failedImages;
      }),
      concatMap((sortedFiles) => from(sortedFiles)),
      concatMap((file, index) => {
        return this.wordpressService.uploadMedia(file).pipe(
          tap(() => {}),
          catchError((error) => {
            failedImages++;
            return of(null);
          })
        );
      }),
      filter((result) => result !== null),
      toArray(),
      map((results) => results.map((result) => ({ id: result.id }))),
      tap(() => {})
    );

    return firstValueFrom(imagesObservable as Observable<WooCommerceImage[]>);
  }
#

The test i write

   it('should skip failed downloads and continue uploading others', async () => {
      const vehicleData: HexonVehicleData = {
        afbeeldingen: {
          afbeelding: [
            { url: 'http://example.com/image-1.jpg' },
            { url: 'http://example.com/image-2.jpg' },
          ],
        },
        voertuignr_hexon: 'FAIL123',
      } as HexonVehicleData;

      const queue: Queue = {
        id: 3,
        client: 'Client',
        vendor: 'Vendor',
      } as Queue;
      queueRepositoryMock.findOneByOrFail.mockResolvedValue(queue);

      jest.useFakeTimers();

      httpServiceMock.get.mockImplementation((url) => {
        console.log(url, 'url');
        // TODO fix timeout
        if (url.includes('image-1')) {
          return throwError(() => new Error('Download failed'));
        }
        return of({
          data: Buffer.from('image2'),
          headers: { 'content-type': 'image/jpeg' },
        });
      });

      wordpressServiceMock.uploadMedia.mockReturnValue(
        of({ id: 'uploaded-image-2.jpg' })
      );

      const setQueueStatusSpy = jest
        .spyOn(service, <any>'setQueueStatus')
        .mockImplementation();

      const result = service['createImages2'](vehicleData, 3);

      jest.runAllTicks();
      jest.runAllTimers();

      await expect(result).resolves.toEqual([{ id: 'uploaded-image-2.jpg' }]);
      // expect(result).toEqual([{ id: 'uploaded-image-2.jpg' }]);

      expect(setQueueStatusSpy).toHaveBeenCalled();
      expect(httpServiceMock.get).toHaveBeenCalledTimes(2);
      expect(wordpressServiceMock.uploadMedia).toHaveBeenCalledTimes(1);
    });
haughty sparrow
#

That's alot for just an example. I think this would be a good opportunity to introduce a Rx Marble test, where you can describe a RxJS Observable using Marble syntax to define expected behavior like nexts, errors and completions.

https://rxjs.dev/guide/testing/marble-testing

zenith tusk
#

Is that not only for observables?

haughty sparrow
#

Well yes, but you’re turning it into a Promise? You may question yourself why you’re doing that, but also do the same in your test here?