jest-marbles icon indicating copy to clipboard operation
jest-marbles copied to clipboard

Bug: does not compare error object

Open peterreisz opened this issue 3 years ago • 1 comments

  it('should not pass, but it is', () => {
    const foo$ = cold('#', {}, { foo: 1 });
    const bar$ = cold('#', {}, { bar: 1 });
    expect(foo$).toBeObservable(bar$);
  });

The test above should fail. Tested with version 2.5.1.

peterreisz avatar Jun 23 '21 15:06 peterreisz

The erroneous behavior seems to lie in the toBeNotifications matcher. Here both actual and expected are converted to their marble strings which are then matched for equality. The error value is discarded at this point.

The error occurs if the observable only yields an error. It does not occur if there are more values. So a quick fix in that setup would be to prepend an arbitrary value to the observable.

The following test would fail as intended:

  it('should test error with jest-marbles', () => {
    const testObs$ = throwError('someError').pipe(startWith('asdf'));
    expect(testObs$).toBeObservable(cold('(a#)', {a: 'asdf'}, 'someOtherError'));
  });

One thing to note here is that the prepended value must not be a single character string since it would then be interpreted as a marble. So this

  it('should test error with jest-marbles', () => {
    const testObs$ = throwError('someError').pipe(startWith('a'));
    expect(testObs$).toBeObservable(cold('(a#)', {a: 'a'}, 'someOtherError'));
  });

would wrongfully pass.

The error originates from this piece of code:

function canMarblize(...messages: TestMessage[][]) {
  return messages.every(message => message.filter(({ notification: { kind } }) => kind === 'N').every(isCharacter)); // <-- this is where the error with single character values originates from
}

function isCharacter({ notification: { value } }: TestMessage): boolean {
  return (
    (typeof value === 'string' && value.length === 1) || (value !== undefined && JSON.stringify(value).length === 1)
  );
}

export const customTestMatchers = {
  toBeNotifications(actual: TestMessage[], expected: TestMessage[]) {
    let actualMarble: string | TestMessage[] = actual;
    let expectedMarble: string | TestMessage[] = expected;
    if (canMarblize(actual, expected)) {
      actualMarble = Marblizer.marblize(actual);
      expectedMarble = Marblizer.marblize(expected);
    }

    const pass = equals(actualMarble, expectedMarble);
    ...

May I ask why the values are marbelized here anyway? As far as I can tell it should always be possible to just compare them for equality without comparing their marble-strings?

jBuchholz590 avatar Sep 30 '22 09:09 jBuchholz590