1

I have a SocketService. This class is responsible for socket connection and messaging with a server. I've written some code to make my client able to reconnect on connection loss. It should make 3 attempts with 5 sec delay between them and if no success redirect to login page.

After that I've written a test but I cannot make it work even close to what happening.

I'm trying to simulate 2 errors on send and one successful message. What should result in 3 reconnect attempts and the last successful one.

Here is my test https://stackblitz.com/edit/jasmine-marbles-testing-jdvcnm?file=src/test.spec.ts

I expect a marble diagram to be something near -# 5s # 5s (a|) (in test there is another diagram since I tried different ones).

Sergey
  • 7,184
  • 13
  • 42
  • 85

1 Answers1

1

It looks like you inverted the logic of the mergeMap() operator used in retryWhen(). You should return timer(100) if there are less than 3 attempts, like this:

describe('marble', () => {
  let service: SocketService;

  beforeEach(() => service = new SocketService());

  it('should make 3 attempts and reconnect on 3rd', () => {
    const values = {
      a: {
        status: 200
      }
    };
    const expected = '203ms a';

    (service as any).socket = {};

    spyOn(localStorage, 'getItem').and.returnValue('hello');

    testScheduler.run(({ cold, expectObservable }) => {
      const source$ = createRetryableStream(
        cold('-#'),
        cold('-#'),
        cold('-a', values)
      ).pipe(
        retryWhen(errors => errors.pipe(
          mergeMap((err, i) => {
            return 3 > i
              ? timer(100, testScheduler)
              : throwError(err);
          })
        ))
      );

      // trigger close socket event to make listener work
      service.onClose.next();

      expectObservable(source$).toBe(expected, values);
    });
  });
});

Other than that, expecting more than one terminal event was not correct. You can only have one terminal event (either error or completion) at most (which means that you don't have to have any).

If an error happens in some inner observable and gets handled by an inner operator, it won't be visible from the outside. So, expected values can not be like -# 5s # 5s (a|). Based on marbles '-#', '-#' and '-a' provided to the cold observables, the expected output will be '203ms a' (waiting 3 times 1 ms for 3 - signs + two timers waiting 100 ms for two errors, then emitting a).

Since source observable doesn't have terminal event (the last one, which you were targeting to re-subscribe with retryWhen), you can't have terminal event in expected marbles at all. If you need terminal event, use this -a| in third cold observable and '203ms a|' as expected.

Mladen
  • 2,070
  • 1
  • 21
  • 37
  • @Sergey, I edited my previous answer by removing my suggested solution with `retry` and adding solution based on your attempts with `retryWhen`. – Mladen Sep 07 '19 at 20:44