在带有消息"You provided an invalid object where a stream was expected."的 Jest 测试中订阅史诗时出错



我对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 错误。mergeMap是一个运算符,基本上是一个map()加一个mergeAll()。也就是说,在您提供的回调中,您应该返回类似流的内容。通常这是另一个可观察量,但它也可以是承诺、数组或可迭代。

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,因为除了"史诗"是一种构建 RxJS 代码的模式和ofType()运算符之外,关于 redux-observable 本身几乎没有什么可了解的filter()


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

最新更新