【问题标题】:Error subscribing to epics in Jest test with message "You provided an invalid object where a stream was expected."在 Jest 测试中订阅史诗时出错,并显示消息“您在预期流的位置提供了无效对象。”
【发布时间】:2020-02-09 01:01:27
【问题描述】:

我对 redux-observable 还很陌生,所以我不确定这是一个真正的问题还是一个愚蠢的错误。

我正在尝试将测试添加到我为 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 => {
         |          ^


我正在使用 redux-observable 1.2.0、rxjs 6.5.3

我错过了什么吗?我看到了很多这种类型的例子,它们很有效。

编辑

我真正在做的(除了虚拟示例)是替换基于 redux-thunk Promise 的 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 是唯一返回的操作。 我错过了什么吗?

【问题讨论】:

    标签: rxjs jestjs redux-observable


    【解决方案1】:

    欢迎!

    问题是 RxJS 错误。 mergeMap 是一个基本上是map() 加上mergeAll() 的运算符。也就是说,在您提供的回调中,您应该返回类似流的东西。通常这是另一个 Observable,但也可以是 Promise、Array 或 Iterable。

    mergeMap 是一对多的操作符之一,它与 concatMap、switchMap、exhaustMap 的区别在于它们各自有不同的策略来处理如果在返回的内部值之前发出新的源值会发生什么您投影到的流尚未结束。

    在 mergeMap 的情况下,如果在内部流完成之前发出另一个源值,它将再次调用您的回调(函数式编程术语中的“项目”)并订阅它,同时将任何结果值与先前投影的内部合并流。


    在您的情况下,您在 mergeMap 中返回一个普通的旧 JavaScript 对象,它不是流式的(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(一些罕见的例外),原始操作可能是您的减速器应该处理的。但是您可能只是为了“hello world”风格而这样做,这非常酷。

    也就是说,仍然可以从 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() 运算符)之外,几乎没有关于 redux-observable 本身的知识filter()


    如果你感到困惑——你并不孤单——最好在Stackblitz 中玩耍和/或看看一些更多涉及这个的 RxJS 教程。虽然很难理解,但一旦你理解了,RxJS 解锁的功能对于复杂的异步代码就会变得更加明显。

    【讨论】:

    • 非常感谢@jayphelps,我编辑了我的帖子,通过我正在研究的一个真实示例向您展示真正的问题
    • @emmelazza 看起来你的跟进是一个不同的问题——第一个是关于返回一个预期流的对象。如果你想在 Stackoverflow 上提出新问题,我可以看看,但重要的是不要用不同的后续问题修改问题。
    猜你喜欢
    • 2019-05-23
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    • 2020-06-16
    • 2020-03-10
    • 1970-01-01
    • 2018-11-08
    • 2021-05-21
    相关资源
    最近更新 更多