How do I test an observable inside mergeMap when outer observable uses FileReader

Viewed 53

I'm trying to test a file upload. I wrapped the FileReader() into an observable according to this thread:

onFileSelected(event: any) {
    this.importJsonFileToString(event.target.files[0])
        .pipe(
            map(jsonString => JSON.parse(jsonString)),
            mergeMap(uploadData => this.httpService.postToB(uploadData)),
            tap(() => console.log('after mergeMap'))
        )
        .subscribe();
}

private importJsonFileToString(file: any): Observable<any> {
    return new Observable((subscriber: Subscriber<any>): void => {
        const fileReader = new FileReader();
        fileReader.readAsBinaryString(file);
        fileReader.onload = (): void => {
            subscriber.next((<any>fileReader).result.toString());
            subscriber.complete();
        };
        fileReader.onerror = (error: any): void => {
            subscriber.error(error);
        };
    });
}

The problem appears while testing the code. I use the following spec with HttpClientTestingModule to catch the Request of the inner Subscription:

it('should subscribe to inner observable with custom observable', fakeAsync(() => {
    const file = new File(['{"nameA":"nameA"}'], 'testFile', { type: 'text/plain' });
    const dummyEvent = { target: { files: [file] } };

    const parseSpy = spyOn(JSON, 'parse').and.callThrough();

    app.onFileSelected(dummyEvent);

    const postRequest = httpTestingController.expectOne(req => req.method === 'POST');
    postRequest.flush('');
}));

The Request does not get recorded. I also tried adding some ticks because I assume there is a problem with the asynchronous nature of the FileReader.

I also tried outer and inner request instead, which resolves without any problem:

nestedHttpCall() {
    return this.httpService
        .getFromA()
        .pipe(mergeMap(response => this.httpService.postToB(response)))
        .subscribe();
}

Spec:

it('should subscribe to inner observable', () => {
    app.nestedHttpCall();

    const getRequest = httpTestingController.expectOne(req => req.method === 'GET');
    getRequest.flush({ nameA: 'nameA' });

    const postRequest = httpTestingController.expectOne(req => req.method === 'POST');
    postRequest.flush('');

    expect(getRequest.request.method).toBe('GET');
    expect(postRequest.request.method).toBe('POST');
});

Is my approach even correct to test the file upload?

1 Answers

I'm guessing that you are finishing your it() before your asychronous call even starts... which makes no sense to me because you're using fakeAsync, but this might get you closer to an answer.

Try

asynch onFileSelected(event: any) {
  return this.importJsonFileToString(event.target.files[0]).pipe(
    map(jsonString => JSON.parse(jsonString)),
    mergeMap(uploadData => this.httpService.postToB(uploadData)),
    tap(() => console.log('after mergeMap'))
  ).toPromise();
}

And then you can use await to ensure it finishes.

it('should subscribe to inner observable with custom observable', fakeAsync(() => {
    const file = new File(['{"nameA":"nameA"}'], 'testFile', { type: 'text/plain' });
    const dummyEvent = { target: { files: [file] } };

    const parseSpy = spyOn(JSON, 'parse').and.callThrough();

    await app.onFileSelected(dummyEvent);

    const postRequest = httpTestingController.expectOne(req => req.method === 'POST');
    postRequest.flush('');
}));

Oh wait!! You're not using tick()! That's most likely the issue.

Related