Search code examples
unit-testingjasminerxjsjasmine-marbles

Marble testing a subject's behavior over time with jasmine-marbles


So I'm trying to test a Subject's behavior and it's not working, and it seems like there some things I'm not understanding correctly. Consider the following test:

it('marble testing subject test', () => {
    const obs$: Subject<number> = new Subject<number>();

    obs$.next(42);
    obs$.next(24);

    expect(obs$.asObservable()).toBeObservable(hot('xy', { x: 42, y: 24 }));
  });

This fails with this error message:

Expected $.length = 0 to equal 2.
Expected $[0] = undefined to equal Object({ frame: 0, notification: Notification({ kind: 'N', value: 42, error: undefined, hasValue: true }) }).
Expected $[1] = undefined to equal Object({ frame: 10, notification: Notification({ kind: 'N', value: 24, error: undefined, hasValue: true }) }).

I think I sort of understand why: the Subject (per documentation) only emits values after the start of the subscription. The toBeObservable() function (I'm assuming) subscribes to the Subject and so my 2 next calls happened before this, so they won't emit.

So my question is, how would I test something like this? i.e. testing a series of emissions from a subject over time? Is this possible to do with marble testing? I can sort of get it to work by changing it out for a ReplaySubject but if I do, the marble diagram has to be (xy) instead of xy.

Thanks.


Solution

  • I have this working in the context of an Angular application

    My service, the key part being that I define my things as a getter, giving me a chance to actually spy on asObservable if I just defined things$ as a property then the spy doesn't work:

    @Injectable({
      providedIn: 'root'
    })
    export class ApiService {
    
      private things = new BehaviorSubject<Array<string>>([]);
    
      public get things$(): Observable<boolean> {
        return this.things.asObservable().pipe(map((things) => things.length > 0))
      }
    }
    
    

    And then in my test, I guess the key part is that I am spying on the asObservable method on the things BehaviourSubject. Like this:

    describe('#things$', () => {
    
        it('should ', () => {
          const things.   = 'a-b-a';
          const expected =  't-f-t';
          // spy on
          spyOn(service['things'], 'asObservable').and.returnValue(hot(things, {
            a: ['a', 'b'],
            b: []
          }));
    
          expect(service.things$).toBeObservable(cold(expected, {
            t: true,
            f: false
          }));
        });
      });