如何测试包含去抖操作符的observable?

时间:2016-05-25 04:28:32

标签: jasmine rxjs

如何使用debounce运算符编写Jasmine测试以测试observable?我跟着this blog post了解了应该如何测试的原则,但它似乎没有用。

以下是我用来创建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;

请注意,测试从不调用tap运算符。但是,如果我在浏览器上运行此代码,它将正确执行。

以下是我对测试的尝试:

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);
  }
});

当我运行测试时,假的debounce永远不会被调用。一旦我越过username,我打算嘲笑debounce服务。

2 个答案:

答案 0 :(得分:1)

在测试代码中,您将触发scheduleRelative函数内的输入事件。这不起作用,因为您在进行更改之前提前1000ms。然后,debouncer等待500ms去除isAvailable呼叫,但你已经停止了调度程序,所以时间不会在之后推进。

你应该做的是:在推进调度程序时间之前触发输入事件,或者在scheduleRelative函数中触发输入事件一段时间< = 500ms,然后在scheduleRelative函数内部触发1000ms你必须使用预期的输出调用expect函数,然后停止调度程序。

它应该是这样的:

  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);
  });

除此之外,我对scheduleAbsolute而不是scheduleRelative有更好的体验,因为它不那么令人困惑。

答案 1 :(得分:1)

根据Simon Jentsch的回答,以下是使用scheduleAbsolute代替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);
  }

});