How to check observable containing debounce statement?

How to write a Jasmine test to test the observable using the debounce statement? I followed this blog post and understood the principles of testing it, but it just doesn't work.

Below is the factory that I use to create the observable:

 import Rx from "rx/dist/rx.all"; import DOMFactory from "../utils/dom-factory"; import usernameService from "./username.service"; function createUsernameComponent(config) { const element = DOMFactory(config); const username = Rx.Observable .fromEvent(element.find('input'), 'input') .pluck('target', 'value') .startWith(config.value); const isAvailable = username .debounce(500) .tap(() => console.info('I am never called!')) .flatMapLatest(usernameService.isAvailable) .startWith(false); const usernameStream = Rx.Observable.combineLatest(username, isAvailable) .map((results) => { const [username, isAvailable] = results; return isAvailable ? username : '' }) .distinctUntilChanged(); return Object.freeze({ stream: usernameStream, view: element }); } export default createUsernameComponent; 

Note that the tap statement is never called by the test. However, it will execute correctly if I run this code in a browser.

Below is my test attempt:

 import Rx from "rx/dist/rx.all"; import Username from "./username.component"; import DataItemBuilder from "../../../test/js/utils/c+j-builders"; import usernameService from "./username.service" describe('Username Component', () => { let input, username; beforeEach(() => { const usernameConfig = DataItemBuilder.withName('foo') .withPrompt('label').withType('text').build(); const usernameComponent = Username(usernameConfig); usernameComponent.stream.subscribe(value => username = value); input = usernameComponent.view.find('input'); }); it('should set to a valid username after debounce', () => { const scheduler = injectTestSchedulerIntoDebounce(); scheduler.scheduleRelative(null, 1000, () => { doKeyUpTest('abcddd', 'abcdd'); scheduler.stop(); }); scheduler.start(); scheduler.advanceTo(1000); }); function injectTestSchedulerIntoDebounce() { const originalOperator = Rx.Observable.prototype.debounce; const scheduler = new Rx.TestScheduler(); spyOn(Rx.Observable.prototype, 'debounce').and.callFake((dueTime) => { console.info('The mocked debounce is never called!'); if (typeof dueTime === 'number') { return originalOperator.call(this, dueTime, scheduler); } return originalOperator.call(this, dueTime); }); return scheduler; } function doKeyUpTest(inputValue, expectation) { input.val(inputValue); input.trigger('input'); expect(username).toBe(expectation); } }); 

When I run the test, the debounce fake will never be called. I plan to mock the username service when I can get past debounce .

+6
source share
2 answers

In the test code, you fire an input event inside the scheduleRelative function. This does not work because you execute 1000 ms before making changes. The debouncer then waits for 500 ms to cancel the isAvailable call, but you have already stopped the scheduler so that the time does not advance later.

What you need to do: raise an input event before increasing the scheduler time or even better in the scheduleRelative function for time <= 500 ms in a, and then inside the scheduleRelative function for 1000 ms, which you should call expect with the expected output, and then stop the scheduler.

It should look like this:

  it('should set to a valid username after debounce', () => { const scheduler = injectTestSchedulerIntoDebounce(); scheduler.scheduleRelative(null, 500, () => { input.val(inputValue); input.trigger('input'); }); scheduler.scheduleRelative(null, 1000, () => { expect(username).toBe(expectation); scheduler.stop(); }); scheduler.start(); scheduler.advanceTo(1000); }); 

In addition to this, I have better experience with scheduleAbsolute instead of scheduleRelative because it is less confusing.

+1
source

According to the answer of Simon Gents, below is the answer using scheduleAbsolute instead of scheduleRelative :

 import Rx from "rx/dist/rx.all"; import Username from "./username.component"; import DataItemBuilder from "../../../test/js/utils/c+j-builders"; import usernameService from "./username.service" describe('Username Component', () => { let input, username, promiseHelper; const scheduler = new Rx.TestScheduler(0); beforeEach(() => { spyOn(usernameService, 'isAvailable').and.callFake(() => { return Rx.Observable.just(true); }); }); beforeEach(() => { const usernameConfig = DataItemBuilder.withName('foo') .withPrompt('label').withType('text').build(); const usernameComponent = Username(usernameConfig, scheduler); usernameComponent.stream.subscribe(value => username = value); input = usernameComponent.view.find('input'); }); it('should set the username for valid input after debounce', (done) => { doKeyUpTest('abcddd', ''); scheduler.scheduleAbsolute(null, 100, () => { expect(usernameService.isAvailable).not.toHaveBeenCalled(); expect(username).toBe(''); }); scheduler.scheduleAbsolute(null, 1000, () => { expect(usernameService.isAvailable).toHaveBeenCalled(); expect(username).toBe('abcddd'); scheduler.stop(); done(); }); scheduler.start(); }); function doKeyUpTest(inputValue, expectation) { input.val(inputValue); input.trigger('input'); expect(username).toBe(expectation); } }); 
+1
source

All Articles