Den*_*jas 6 rxjs marble ngrx angular
我在测试对我的效果的失败操作时遇到问题。
在这里给出一些上下文,当调用Load操作时会执行 loadProducts 效果。在效果内部执行 HTTP 请求,如果此请求成功执行,则调用LoadSuccess操作,否则调用 LoadFail。代码如下
@Effect()
loadProducts$ = this.actions$.pipe(
ofType(productActions.ProductActionTypes.Load),
mergeMap((action: productActions.Load) =>
this.productService.getProducts().pipe(
map((products: Product[]) => (new productActions.LoadSuccess(products))),
catchError(error => of(new productActions.LoadFail(error)))
))
);
Run Code Online (Sandbox Code Playgroud)
为了测试这种效果,我使用了与 jasmine-marbles 几乎相同的 jest-marbles,无论如何,我将 Load 操作创建为热可观察对象,将 http 响应创建为冷和默认的预期结果。
it('should return a LoadFail action, with an error, on failure', () => {
const action = new Load();
const errorMessage = 'Load products fail';
const outcome = new LoadFail(errorMessage);
actions$ = hot('-a', { a: action});
const response = cold('-#|', {}, errorMessage);
productServiceMock.getProducts = jest.fn(() => response);
const expected = cold('--(b|)', { b: outcome });
expect(effects.loadProducts$).toBeObservable(expected);
});
Run Code Online (Sandbox Code Playgroud)
当我运行测试时抛出一个错误,说我的 loadProducts observable 和预期的结果不匹配。
? should return a LoadFail action, with an error, on failure (552ms)
Product effects › loadProducts › should return a LoadFail action, with an error, on failure
expect(received).toBeNotifications(expected)
Expected notifications to be:
[{"frame": 20, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"payload": "Load products fail", "type": "[Product] Load Fail"}}}, {"frame": 20, "notification": {"error": undefined, "hasValue": false, "kind": "C", "value": undefined}}]
But got:
[{"frame": 20, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"payload": "Load products fail", "type": "[Product] Load Fail"}}}]
Difference:
- Expected
+ Received
Array [
Object {
"frame": 20,
"notification": Notification {
"error": undefined,
"hasValue": true,
"kind": "N",
"value": LoadFail {
"payload": "Load products fail",
"type": "[Product] Load Fail",
},
},
},
- Object {
- "frame": 20,
- "notification": Notification {
- "error": undefined,
- "hasValue": false,
- "kind": "C",
- "value": undefined,
- },
- },
]
Run Code Online (Sandbox Code Playgroud)
我知道错误是什么,但我不知道如何解决它。我知道弹珠测试世界
我想首先解释为什么它不起作用。
如您所知,当您使用大理石图测试 observable 时,您使用的不是实时时间,而是虚拟时间。虚拟时间可以用frames. 框架的值可能会有所不同(例如10,1),但无论值如何,它都有助于说明您正在处理的情况。
例如,使用hot(--a---b-c),您描述了一个将发出以下值的 observable:aat 2u、bat6u和cat 8u( u- 时间单位)。
在内部,RxJs 创建一个动作队列,每个动作的任务是发出分配给它的值。{n}u描述动作何时完成其任务。
对于hot(--a---b-c),动作队列看起来像这样(大致):
queue = [
{ frame: '2u', value: 'a' }/* aAction */,
{ frame: '6u', value: 'b' }/* bAction */,
{ frame: '8u', value: 'c' }/* cAction */
]
Run Code Online (Sandbox Code Playgroud)
hot和cold,当被调用时,将分别实例化 ahot和coldobservable 。它们的基类扩展了Observable类。
现在,看看当您处理内部 observable 时会发生什么非常有趣,如您的示例中所遇到的:
actions$ = hot('-a', { a: action}); // 'a' - emitted at frame 1
const response = cold('-#|', {}, errorMessage); // Error emitted at 1u after it has been subscribed
productServiceMock.getProducts = jest.fn(() => response);
const expected = cold('--(b|)', { b: outcome }); // `b` and `complete` notification, both at frame 2
Run Code Online (Sandbox Code Playgroud)
将response观察到的是,由于订阅a,这意味着错误通知将在发射frame of a+ original frame。也就是说,frame 1(a的到达) + frame1(发出错误时) = frame 2。
那么,为什么hot('-a')不起作用?
这是因为mergeMap处理事情的方式。在 usingmergeMap及其兄弟节点时,如果源完成但操作符具有仍处于活动状态的内部 observable(尚未完成),则不会传递源的完成通知。只有当所有内部 observables 也完成时才会这样。
另一方面,如果所有内部 observable 都完成了,但源没有完成,则没有完整的通知要传递给链中的下一个订阅者。这就是它最初不起作用的原因。
现在,让我们看看为什么它会这样工作:
actions$ = hot('-a|', { a: action});
const response = cold('-#|)', {}, errorMessage);
productServiceMock.getProducts = jest.fn(() => response);
const expected = cold('--(b|)', { b: outcome });
Run Code Online (Sandbox Code Playgroud)
该动作的队列现在看起来是这样的:
queue = [
{ frame: '1u', value: 'a' },
{ frame: '2u', completeNotif: true },
]
Run Code Online (Sandbox Code Playgroud)
当a收到时,response将被订阅,并且因为它是一个使用创建的可观察对象cold(),它的通知必须分配给操作并相应地放入队列中。
之后response已订阅,队列是这样的:
queue = [
// `{ frame: '1u', value: 'a' },` is missing because when an action's task is done
// the action itself is removed from the queue
{ frame: '2u', completeNotif: true }, // Still here because the first action didn't finish
{ frame: '2u', errorNotif: true, name: 'Load products fail' }, // ' from '-#|'
{ frame: '3u', completeNotif: true },// `|` from '-#|'
]
Run Code Online (Sandbox Code Playgroud)
请注意,如果应在同一帧发出 2 个队列操作,则最旧的将优先。
由上可知,source 会在内部 observable 发出错误之前发出完整的通知,这意味着当内部 observable 发出捕获 error( outcome) 的值时,mergeMap将传递完整的通知。
最后,(b|)需要 incold('--(b|)', { b: outcome });因为catchError订阅的 observableof(new productActions.LoadFail(error)))将在同一帧内发出和完成。当前帧保存当前选定动作的帧的值。在这种情况下,是2,来自{ frame: '2u', errorNotif: true, name: 'Load products fail' }。
我找到了一种方法来解决我的问题,不确定是最好的方法,但基本上我添加了一个管道来完成热可观察。如果还有其他解决方案,请告诉我。
it('should return a LoadFail action, with an error, on failure', () => {
const action = new Load();
const errorMessage = 'Load products fail';
const outcome = new LoadFail(errorMessage);
actions$ = hot('-a|', { a: action});
const response = cold('-#|)', {}, errorMessage);
productServiceMock.getProducts = jest.fn(() => response);
const expected = cold('--(b|)', { b: outcome });
expect(effects.loadProducts$).toBeObservable(expected);
});
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1180 次 |
| 最近记录: |