Ошибка подписки на эпос в тесте Jest с сообщением «Вы указали неверный объект, где ожидался поток». - PullRequest
0 голосов
/ 11 октября 2019

Я довольно новичок в наблюдении за редуксом, поэтому я не уверен, является ли это реальной проблемой или просто глупой ошибкой.

Я пытаюсь добавить тест к эпосам, которые я написал для Reactприложения, но я не могу их проверить. Вот пример.

Мой пример эпоса:

const example = action$ =>
     action$.pipe(
        ofType('my_type'),
        mergeMap(() => {
            return { type: 'result_type'}
        })
    ); 

Я пытался протестировать его самым простым способом:

it('simple test', done => {
    const action$ = ActionsObservable.of({ type: 'my_type'});
    const state$ = null;

    return myEpics.example(action$, state$)
        .subscribe(actions => {
            expect(actions).toEqual({ type: 'result_type'});
            done();
        })

});

Используя этот код, я получаюследующая ошибка:

TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

      12 | 
      13 |     return myEpics.example(action$, state$)
    > 14 |         .subscribe(actions => {
         |          ^


Я использую наблюдаемую приставку 1.2.0, rxjs 6.5.3

Я что-то пропустил? Я видел много примеров этого типа, и они работают.

РЕДАКТИРОВАТЬ

То, что я действительно делаю (кроме фиктивного примера), заменяет обещание с избыточным количеством словпромежуточное программное обеспечение на основе API (со многими асинхронными вызовами к конечной точке ReST). Я был в состоянии заставить это работать, но о тестировании я немного запутался. Я видел подход с мраморной диаграммой и подписку на эпик, добавляющую ожидания, например, в отношении вызовов API, конечных действий. Я использую последнее, но, например, для эпоса, возвращающего несколько действий, я получаю только первое в подписке.

«сложный» пример:

export const handleLogIn = (action$, state$) =>
    action$.pipe(
        ofType(authTypes.LOG_IN),
        withLatestFrom(state$),
        mergeMap(([{ payload: credentials}, { router }]) =>
            from(ApiService.logIn(credentials))
                .pipe(
                    mergeMap(authToken => of(authActions.set_auth_token(authToken), navigationActions.goTo(router.location.state ? router.location.state.pathname : baseRoutes.START),
                    catchError(message => of(authActions.set_auth_error(message)))
                )
        )
    );

Тест

it('should handle LOG_IN navigating to base path', done => {
    const token = 'aToken';
    const credentials = { username: 'user', password: 'password' };
    const action$ = ActionsObservable.of(authActions.log_in(credentials));
    const state$ = new StateObservable(new Subject(), { router: { location: { state: null }}});

    spyOn(ApiService, 'logIn').and.returnValue(Promise.resolve(token));

    authEpics.handleLogIn(action$, state$)
        .subscribe(actions => {
            expect(ApiService.logIn).toHaveBeenCalledWith(credentials);
            expect(actions).toEqual([
                authActions.set_auth_token(token),
                navigationActions.go_to(baseRoutes.START)
            ]);
            done();
        });

});

тест не пройден, поскольку только authActions.set_auth_token является единственным возвращаемым действием. Я что-то упустил?

1 Ответ

0 голосов
/ 12 октября 2019

Добро пожаловать!

Ошибка RxJS. mergeMap - это оператор, который в основном представляет собой map() плюс mergeAll(). То есть внутри предоставляемого вами обратного вызова вы должны вернуть что-то похожее на поток. Обычно это другой Observable, но вместо этого он может быть Promise, Array или Iterable.

mergeMap - это один из операторов типа «один ко многим», с разницей между ним и concatMap, switchMap и выхлопными картами, которыеу каждого из них есть разные стратегии для обработки того, что должно произойти, если новое исходное значение испускается до того, как возвращенный внутренний поток, на который вы спроецировали, еще не закончил.

В случае mergeMap, если испускается другое исходное значениеперед завершением внутреннего потока он снова вызовет ваш обратный вызов («проект» в терминах функционального программирования) и подпишется на него при объединении любых результирующих значений с ранее спроектированным внутренним потоком.


В вашем случае,вы возвращаете Простой объект JavaScript внутри mergeMap, который не похож на поток (Observable, Promise, Array или Iterable). Если на самом деле вы просто хотите сопоставить одно действие другому действию 1: 1, вы можете просто использовать map:

import { map } from 'rxjs/operators';

const example = action$ =>
  action$.pipe(
    ofType('my_type'),
    map(() => {
      return { type: 'result_type' };
    })
); 

Но имейте в виду, что это не обычно идиоматический Redux (некоторые редкие исключения) исходное действие, вероятно, должно обрабатываться вашими редукторами. Но, возможно, вы просто делаете это для стиля «привет мир», что очень круто.

Тем не менее, все еще возможно возвращать одно (или более) значений синхронно изнутри mergeMap. Вы можете использовать of()

import { of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

const example = action$ =>
  action$.pipe(
    ofType('my_type'),
    map(() => {
      return of({ type: 'result_type' });
      // or more than one
      // return of({ type: 'first'}, { type: 'second' }, ...etc);
    })
); 

Помните: redux-observable - это небольшая вспомогательная библиотека для выполнения RxJS + Redux. Это означает, что на самом деле вы будете изучать RxJS и Redux, так как очень мало что можно узнать о самовосприимчивости за редуксом, за исключением того, что «Epic» является шаблоном структурирования вашего кода RxJS и оператором ofType(), который является сахаром сверху. из filter()


Если вы растерялись - вы не одиноки - лучше всего поиграть в Stackblitz и / или взглянуть нанекоторые учебники RxJS, которые касаются этого больше. Хотя это может быть трудно понять, но как только вы это сделаете, способности, которые разблокирует RxJS, становятся намного более очевидными для сложного асинхронного кода.

...