Marble test fails with Jest, but equivalent test succeeds with Jasmine
Asked Answered
N

1

6

I'm trying to convert my unit tests from Jasmine to Jest. Some tests started to fail after converting them to Jest. Can someone explain why they fail with Jest.

I managed to isolate the problem to the test case below.

With Jasmine is runs successful:

import { JasmineMarble } from './jasmine-marble';
import { cold } from 'jasmine-marbles';
import { switchMap } from 'rxjs/operators';
import { EMPTY, Observable } from 'rxjs';

class Service {
  foo(): Observable<any> {
    return EMPTY;
  }

  bar(a): Observable<any> {
    return EMPTY;
  }
}

describe('JasmineMarble', () => {
  it('should create an instance', () => {
    const service = new Service();

    spyOn(service, 'foo').and.returnValue(cold('a|', { a: 'A' }));
    spyOn(service, 'bar').and.returnValue(cold('a-b-c|', { a: 'A', b: 'B', c: 'C'}));

    const result$ = service.foo().pipe(switchMap(a => service.bar(a)));

    expect(result$).toBeObservable(cold('a-b-c|', { a: 'A', b: 'B', c: 'C'}));
    expect(service.bar).toHaveBeenCalledWith('A');
  });
});

With Jest it fails with this error trace:

expect(jest.fn()).toHaveBeenCalledWith(expected)

Expected mock function to have been called with:
  ["A"]
But it was not called.

The Jest code:

import { JestMarble } from './jest-marble';
import { cold } from 'jest-marbles';
import { switchMap } from 'rxjs/operators';
import { EMPTY, Observable } from 'rxjs';

class Service {
  foo(): Observable<any> {
    return EMPTY;
  }

  bar(a): Observable<any> {
    return EMPTY;
  }
}

describe('JestMarble', () => {
  it('should create an instance', () => {
    const service = new Service();

    jest.spyOn(service, 'foo').mockReturnValue(cold('a|', { a: 'A' }));
    jest.spyOn(service, 'bar').mockReturnValue(cold('a-b-c|', { a: 'A', b: 'B', c: 'C'}));

    const result$ = service.foo().pipe(switchMap(a => service.bar(a)));

    expect(result$).toBeObservable(cold('a-b-c|', { a: 'A', b: 'B', c: 'C'}));
    expect(service.bar).toHaveBeenCalledWith('A');
  });
});

Can somebody explain this behavior?

You can find an example repository here: https://github.com/stijnvn/marbles The Jasmine example can be run with ng test jasmine-marbles. The Jest one with ng test jest-marbles.

Nimocks answered 16/4, 2019 at 9:38 Comment(3)
As a workaround, I'm now using jasmine-marbles with Jest. Not sure if there are any drawbacks.Nimocks
Same for me - after switching from "jasmine-marbles": "=0.3.1" to "jest-marbles": "=2.3.1", all tests with spies started to fail :(Forcefeed
github.com/meltedspark/jest-marbles/issues/112Forcefeed
G
2

There is now toSatisfyOnFlush introduced to deal with this:

describe('JestMarble', () => {
  it('should create an instance', () => {
    const service = new Service();

    jest.spyOn(service, 'foo').mockReturnValue(cold('a|', { a: 'A' }));
    jest.spyOn(service, 'bar').mockReturnValue(cold('a-b-c|', { a: 'A', b: 'B', c: 'C'}));

    const result$ = service.foo().pipe(switchMap(a => service.bar(a)));

    expect(result$).toBeObservable(cold('a-b-c|', { a: 'A', b: 'B', c: 'C'}));
    expect(output$).toSatisfyOnFlush(() => {
      expect(service.bar).toHaveBeenCalledWith('A');
    });
  });
});
Glogau answered 17/11, 2020 at 17:31 Comment(1)
Because it happened to me using this solution: expect(service.bar).toHaveBeenCalledTimes(1) will fail for some reason because its called twice. If you remove the toSatisfyOnFlush block, it never gets called. Rather odd.Mallorca

© 2022 - 2024 — McMap. All rights reserved.