I have an Angular service with an exposed observable that I'm trying to marble test with the rxjs TestScheduler. A method on the service controls the value the observable emits, using a BehaviorSubject as the source. Here's a really simple example:
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { BehaviorSubject } from "rxjs";
@Injectable()
export class MyService {
private _isVisibleSubject: BehaviorSubject<boolean> = new BehaviorSubject<
boolean
>(true);
isVisible$: Observable<boolean> = this._isVisibleSubject.asObservable();
constructor() {}
toggleVisibility() {
this._isVisibleSubject.next(!this._isVisibleSubject.value);
}
}
A simple unit test for MyService. I want to test 2 conditions:
- The isVisible$ observable starts with true
- When toggleVisibility is called twice, isVisible$ has 3 values, true, false, and back to true
Here's the test class
import { TestBed } from '@angular/core/testing';
import { TestScheduler } from 'rxjs/testing';
import { MyService } from './my.service';
describe('MyService ', () => {
let service: MyService ;
const testScheduler: TestScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
beforeEach(() => {
TestBed.configureTestingModule({ providers: [MyService] });
service = TestBed.inject(MyService);
});
it('Should start out visible', () => {
// This one is easy enough, it starts out visible and nothing else happens
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const values: {[key: string]: boolean} = {'a': true};
const expected = 'a';
expectObservable(service.isVisible$).toBe(expected, values);
});
it('Should toggle visibility back and forth', () => {
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const values: {[key: string]: boolean} = {'a': true, 'b': false};
const expected = 'aba'; // I've also tried with various frames between, eg '-a-b-a'
service.toggleVisibility();
service.toggleVisibility();
expectObservable(service.isVisible$).toBe(expected, values);
});
});
When I run this, the first test passes, but the second fails with an error like Expected $.length = 1 to equal 3.
I have verified that the value actually changes by running a test like:
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const values: {[key: string]: boolean} = {'b': false};
const expected = 'b';
service.toggleVisibility();
expectObservable(service.isVisible$).toBe(expected, values);
});
I'm assuming the issue is that service.isVisible$ isn't subscribed to until expectObservable() is called, so the previous values are lost. I looked through the rxjs documentation on marble testing but can't figure this out.
I realize I could just set up a subscription manually to the isVisible$ observable when the test starts, and verify state along the way as I make changes, but that doesn't feel as nice as using the marble testing.
Is what I want to do possible?
.next
call. – ps2goat