简体   繁体   中英

What is the correct way of testing chained catchError functions

I'm trying to write a jasmine test for an @Effect that has chained rxjs catchError operators, but am struggling to test any observables beyond the first catchError .

here's the effect:

@Effect() submitEndsheets$ = this.actions$.pipe(
    ofType<SubmitEndSheets>(SpreadActionTypes.SUBMIT_ENDSHEETS),
    withLatestFrom(this.store.pipe(select(fromAppStore.fromOrder.getDocumentId))),
    concatMap(([action, documentId]) =>
        this.spreadService.submitEndSheets(documentId).pipe(
            map((response: ActionProcessorDto) => new SubmitEndSheetsSuccess(response.data)),
            catchError((error) => of(undo(action))),
            catchError((error) => of(new MessageModal({
                    message: error.message,
                    title: 'Submission Error!'
                })
            ))
        )
    )
);

and the corresponding test:

it('handles errors by sending an undo action', () => {
        const action = {
            type: SpreadActionTypes.SUBMIT_ENDSHEETS,
        };
        const source = cold('a', { a: action });
        const error = new Error('Error occurred!');
        const service = createServiceStub(error);
        const store = createStoreState();
        const effects = new Effects(service, new Actions(source), store);

        const expected = cold('ab', {
           a: undo(action),
            b: new MessageModal({
                message: 'Sorry, something went wrong with your request. Please try again or contact support.',
                title: 'Update Error!'
            }),
        });
        expect(effects.submitEndsheets$).toBeObservable(expected);
    });

for reference, here are the createServiceStub that mocks the service and createStoreState that, you guessed it, creates a mock store.

function createServiceStub(response: any) {
    const service = jasmine.createSpyObj('spreadService', [
        'load',
        'update',
        'updateSpreadPosition',
        'submitEndSheets'
    ]);

    const isError = response instanceof Error;
    const serviceResponse = isError ? throwError(response) : of(response);

    service.load.and.returnValue(serviceResponse);
    service.update.and.returnValue(serviceResponse);
    service.updateSpreadPosition.and.returnValue(serviceResponse);
    service.submitEndSheets.and.returnValue(serviceResponse);

    return service;
}

function createStoreState() {
    const store = jasmine.createSpyObj('store', ['pipe']);
    store.pipe.and.returnValue(of({ documentId: 123 }));

    return store;
}

here's the test output:

FAILED TESTS:
      ✖ handles errors by sending an undo action
        HeadlessChrome 0.0.0 (Mac OS X 10.14.2)
      Expected $.length = 1 to equal 2.
      Expected $[1] = undefined to equal Object({ frame: 10, notification: Notification({ kind: 'N', value: MessageModal({ payload: Object({ message: 'Sorry, something went wrong with your request. Please try again or contact support.', title: 'Update Error!' }), type: 'MESSAGE_MODAL' }), error: undefined, hasValue: true }) }).
          at compare node_modules/jasmine-marbles/bundles/jasmine-marbles.umd.js:389:1)
          at UserContext.<anonymous> src/app/book/store/spread/spread.effects.spec.ts:197:46)
          at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)

Thanks in advance for any help!

Update: catchError can send an array of actions back out of the effect like so:

@Effect() submitEndsheets$ = this.actions$.pipe(
    ofType<SubmitEndSheets>(SpreadActionTypes.SUBMIT_ENDSHEETS),
    withLatestFrom(this.store.pipe(select(fromAppStore.fromOrder.getDocumentId))),
    concatMap(([action, documentId]) =>
        this.spreadService.submitEndSheets(documentId).pipe(
            map((response: ActionProcessorDto) => new SubmitEndSheetsSuccess(response.data)),
            catchError(error => [
                new PopSingleToast({
                    severity: ToastSeverity.error,
                    summary: 'Failure',
                    detail: `Some error occurred: \n Error: ${error}`
                }),
                undo(action)
            ])
        )
    )
);

The corresponding test looks like so:

it('handles errors by sending an undo action', () => {
        const action = {
            type: SpreadActionTypes.SUBMIT_ENDSHEETS
        };
        const source = cold('a', { a: action });
        const error = new Error('Error occurred!');
        const service = createServiceStub(error);
        const store = createStoreState();
        const effects = new Effects(service, new Actions(source), store);

        const expectedAction = new PopSingleToast({
            severity: ToastSeverity.error,
            summary: 'Failure',
            detail: `Some error occurred: \n Error: ${error}`
        });

        const expected = cold('(ab)', {
            a: expectedAction,
            b: undo(action)
        });

        expect(effects.submitEndsheets$).toBeObservable(expected);
    });

Thanks for to all for the help!

Having two catchErrors in a row like this means the second one will never trigger because the first one will eat the error.

You would need to rethrow the error in the first catchError to get inside the second one:

catchError(error => throw new Error()),
catchError(error => console.log('now I trigger'))

So I am afraid your questions doesn't make really sense.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM