测试失败动作-大理石-NGRX效果 [英] Testing fail action - marble - ngrx Effects

查看:93
本文介绍了测试失败动作-大理石-NGRX效果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

测试效果失败的动作时出现问题.

I've got an issue testing a failed action on my effects.

在此处提供一些上下文信息,是在调用 Load 操作时执行loadProducts效果.在效果内部,如果成功执行此请求,则会调用 LoadSuccess 操作,否则将调用 LoadFail .下面的代码

To give a bit of context here loadProducts effect is executed when the Load action is called. Inside the effect an HTTP request is performed, in case this request is executed successfully the LoadSuccess action is called, otherwise LoadFail is called. Code here bellow

  @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)))
      ))
  );

为了测试这种效果,我使用了与茉莉花大理石几乎相同的玩笑大理石,无论如何,我将Load操作创建为可观察到的热响应,将http响应创建为可察觉的响应,并创建了默认预期结果.

To test this effect I used jest-marbles that is pretty much the same than jasmine-marbles, anyway, I created Load action as a hot observable, my http response as a cold and the default expected outcome.

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);
});

当我运行测试时,抛出一个错误,表明我的loadProducts可观察到,并且预期结果不匹配.

When I run the test throws an error saying my loadProducts observable and the expected outcome does not match.

  ✕ 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,
-     },
-   },
  ]

我知道错误是什么,但我不知道如何解决.我在大理石测试界很了解

I know what the error is but I have no idea how to solve it. I am knew on the marbles testing world

推荐答案

我想解释一下为什么它最初不起作用.

I'd like to explain why it didn't work in the first place.

如您所知,当您使用大理石图测试可观察物时,您不是在使用实时,而是在虚拟时间.可以在frames中测量虚拟时间.帧的值可以变化(例如101),但是不管值如何,它都有助于说明您正在处理的情况.

As you know, when you're testing observables using marble diagrams, you're not using the real time, but a virtual time. Virtual time can be measured in frames. The value of a frame can vary(e.g 10, 1), but regardless of the value, it's something that helps illustrating the situation you're dealing with.

例如,使用hot(--a---b-c),您将描述一个可观察对象,该对象将发出以下值:2u处的a6u处的b8u处的c(u-时间单位).

For example, with hot(--a---b-c), you describe an observable that will emit the following values: a at 2u, b at 6u and c at 8u(u - units of time).

在内部,RxJs创建一个动作队列,每个动作的任务是发出已分配的值. {n}u描述了该操作何时执行其任务.

Internally, RxJs creates a queue of actions and each action's task is to emit the value that it has been assigned. {n}u describes when the action will do its task.

对于hot(--a---b-c)操作队列如下所示(大致):

For hot(--a---b-c), the action queue would look like this(roughly):

queue = [
  { frame: '2u', value: 'a' }/* aAction */, 
  { frame: '6u', value: 'b' }/* bAction */, 
  { frame: '8u', value: 'c' }/* cAction */
]

hotcold在被调用时将分别实例化一个可观察的hotcold.他们的基类扩展了Observable类.

hot and cold, when called, will instantiate a hot and cold observable, respectively. Their base class extends the Observable class.

现在,看看您在处理内部可观测对象时会发生什么非常有趣,就像您在示例中遇到的那样:

Now, it's very interesting to see what happens when you're dealing with inner observables, as encountered in your example:

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

由于a,预订了response观察对象,这意味着将在frame of a + original frame处发出错误通知.也就是说,frame 1(a的到来)+ frame1(当产生错误时)= frame 2.

The response observable is subscribed due to a, meaning that the error notification will be emitted at frame of a + original frame. That is, frame 1(a's arrival) + frame1(when the error is emitted) = frame 2.

那么,为什么hot('-a')不起作用?

So, why did hot('-a') not work ?

这是因为mergeMap处理事情的方式.使用mergeMap及其同级元素时,如果源已完成但操作员具有仍处于活动状态的内部可观察对象(尚未完成),则不会传递源的完整通知.只有当所有内在的可观观测也都完成时.

This is because of how mergeMap handles things. When using mergeMap and its siblings, if the source completes but the operator has inner observables that are still active(did not complete yet), the source's complete notification won't be passed along. It will be only when all the inner observables complete as well.

另一方面,如果所有内部可观察值都已完成,但源未完成,则没有完整的通知要传递给链中的下一个订户. 这就是为什么它最初没有起作用的原因.

On the other hand, if all the inner observables complete, but the source didn't, there is no complete notification to be passed along to the next subscriber in the chain. This is why it hadn't worked initially.

现在,让我们看看为什么它会这样工作:

Now, let's see why it does work this way:

actions$ = hot('-a|', { a: action});

const response = cold('-#|)', {}, errorMessage);
productServiceMock.getProducts = jest.fn(() => response);

const expected = cold('--(b|)', { b: outcome });

操作队列现在看起来像这样:

queue = [
  { frame: '1u', value: 'a' },
  { frame: '2u', completeNotif: true },
]

当接收到a时,将预订response,并且由于它是使用cold()创建的可观察对象,因此必须将其通知分配给操作并相应地放入队列中

When a is received, the response will be subscribed and because it's an observable created with cold(), its notifications will have to be assigned to actions and put in the queue accordingly.

预订response后,队列将如下所示:

After response has been subscribed to, the queue would look like this:

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 '-#|'
]

请注意,如果应在同一帧上发出2个队列操作,则以最早的一个为准.

Notice that if 2 queue actions should be emitted at the same frame, to oldest one will take precedence.

从上面我们可以看出,源将发出完整的通知,之前,内部可观察对象将发出错误,这意味着内部可观察对象将发出由于捕获错误而导致的值(outcome),mergeMap将传递完整的通知.

From the above, we can tell that the source will emit a complete notification before the inner observable emits the error, meaning that when the inner observable will emit the value resulted from catching the error(outcome), the mergeMap will pass along the complete notification.

最后,在cold('--(b|)', { b: outcome });中需要(b|),因为catchError所预订的可观察物of(new productActions.LoadFail(error)))将在同一帧内发射并完成.当前帧保存当前选定动作的帧的值.在这种情况下,2来自{ frame: '2u', errorNotif: true, name: 'Load products fail' }.

Finally, (b|) is needed in cold('--(b|)', { b: outcome }); because the observable which catchError subscribes to, of(new productActions.LoadFail(error))), will emit and complete within the same frame. The current frame holds the value of the current selected action's frame. In this case, is 2, from { frame: '2u', errorNotif: true, name: 'Load products fail' }.

这篇关于测试失败动作-大理石-NGRX效果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆