Cant get observable with embedded setInterval to work in test

Viewed 113

I've written a little controller class for countdown timer. Works fine on the web page, but I can't get my unit test to work. I'm not sure if the problem is the way I'm running my test, or the something in the class itself.

Here is the class under test:

import { Observable, Subscription } from 'rxjs'

    export class CountDownTimerController {
         public secondsTimer$ : Observable<any>;
         private counter : number;
         private _counterActive: boolean;
        
         public get counterActive(): boolean {
            return this._counterActive;
        }
         
        constructor() {
            this.counter = -1;
            this._counterActive = false;    
            this.startTicker();
         }
    
         start() {
            this._counterActive = true;
            console.clear();
            console.log(`Started, _counterActive is ${this._counterActive}`)
         }
         pause() {
            this._counterActive = false;
         }
         reset() {
             this.pause()  
             this.counter = 0;
         }
         public get count() : number {
            return this.counter
         }
         private startTicker() { 
             if (this.secondsTimer$) {return}
             let self = this;
   
            this.secondsTimer$ = new Observable(observer => {
                let interval = setInterval(() => {
                    console.log(`Time: ${Date.now()} interval triggered.  counterActive ${self._counterActive} counter: ${self.counter}`)
                    if (self._counterActive) {
                        self.counter = self.counter + 1;
                        observer.next(self.counter)
                    }
                }, 1000);
                return function unsubscribe() {clearInterval(interval);
                }
            })
         }
    }

Here is the test I'm running:

fit('starts counting seconds when start is called', async ()=>{
        console.log("starrt test")
        let controller = new CountDownTimerController();
        expect(controller.count).toBe(-1,"Should be initialized to -1");
        expect(controller.counterActive).toBeFalse;
        controller.start(); 
        expect(controller.counterActive).toBeTrue
        console.log(`Time: ${Date.now()} controller started`)
        await utils.sleep(2000);  //WAIT FOR TWO TICKS OF SetInterval
        expect(controller.counterActive).toBeTrue();
        console.log(`Time: ${Date.now()} Waited 2 seconds`)
        console.log(`Counter: ${controller.count}`)
 --->   expect(controller.count).toBeGreaterThanOrEqual(1,`Ticker should be 1 or more at time ${Date.now()}` )
        //FAILS ON THE PRECEDING LINE
       console.log("end test")
    });

In case of interest, here is utils.sleep implementation:

 export const sleep = (ms: number) => {
    return new Promise(resolve => setTimeout(resolve, ms))
}
 

And here is the console output.

Started, _counterActive is true count-down-timer-controller.spec.ts:37 
Time: 1625597279827 controller started count-down-timer-controller.spec.ts:40 
Time: 1625597281829 Waited 2 seconds count-down-timer-controller.spec.ts:41 
Counter: -1 count-down-timer-controller.spec.ts:44 
end test
debug.js:15 FAILED CountDownTimerController starts counting seconds when start is called
debug.js:21 Error: Expected -1 to be greater than or equal 2, 'Ticker should be 1 or more at time 1625597281829'.
    at <Jasmine>
    at count-down-timer-controller.spec.ts:42
    at <Jasmine>
    at fulfilled (tslib.es6.js:71)
debug.js:6 Skipped 27 tests
1 Answers

You never subscribed to the observable (this.secondsTimer$), so the interval is never created/triggered.

Related