【问题标题】:Mock delay() RxJS with Jest用 Jest 模拟 delay() RxJS
【发布时间】:2019-08-29 07:59:28
【问题描述】:

有没有简单的方法来模拟 RxJS 的 delay() 方法,例如在一个假的时间里?

我有这个方法:

register(user) {
  return this._checkLog(user).delay(500).flatMap( ... )
}

当我删除 delay() 方法时,我的 _register() 测试全部成功。

【问题讨论】:

标签: javascript unit-testing rxjs jestjs


【解决方案1】:

RxJS v6

对于像这样的 RxJS v6 代码:

code.js

import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

export const example = of('hello').pipe(
  delay(1000)
);

...您可以像这样使用sinon fake timers

import * as sinon from 'sinon';
import { example } from './code';

describe('delay', () => {
  let clock;
  beforeEach(() => { clock = sinon.useFakeTimers(); });
  afterEach(() => { clock.restore(); });

  it('should delay one second', () => {
    const spy = jest.fn();
    example.subscribe(spy);

    expect(spy).not.toHaveBeenCalled();  // Success!
    clock.tick(1000);
    expect(spy).toHaveBeenCalledWith('hello');  // Success!
  });
});

(请注意,在撰写本文时 Jest timer mocks 不起作用,不知道为什么)


...或者你可以模拟 delay 来做这样的事情:

import { delay } from 'rxjs/operators';
import { example } from './code';

jest.mock('rxjs/operators', () => {
  const operators = jest.requireActual('rxjs/operators');
  operators.delay = jest.fn(() => (s) => s);  // <= mock delay
  return operators;
});

describe('delay', () => {
  it('should delay one second', () => {
    const spy = jest.fn();
    example.subscribe(spy);

    expect(delay).toHaveBeenCalledWith(1000);  // Success!
    expect(spy).toHaveBeenCalledWith('hello');  // Success!
  });
});

RxJS v5

对于像这样的 RxJS v5 代码:

code.js

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/delay';

export const example = Observable.of('hello').delay(1000);

...你可以模拟 delay 来做这样的事情:

import { Observable } from 'rxjs/Observable';
import { example } from './code';

jest.mock('rxjs/add/operator/delay', () => {
  const Observable = require('rxjs/Observable').Observable;
  Observable.prototype.delay = jest.fn(function () { return this; });  // <= mock delay
});

describe('delay', () => {
  it('should delay one second', () => {
    const spy = jest.fn();
    example.subscribe(spy);

    expect(Observable.prototype.delay).toHaveBeenCalledWith(1000);  // Success!
    expect(spy).toHaveBeenCalledWith('hello');  // Success!
  });
});

【讨论】:

    【解决方案2】:

    要完成 RxJS 6 的 brian-live-outdoor 解决方案,您还可以使用与 jest 配合使用的 delayWhen 和 timer 模拟 delay() 的真实行为:

     jest.mock("rxjs/operators", () => {
      const operators = jest.requireActual("rxjs/operators");
      const observables = jest.requireActual("rxjs");
      operators.delay = jest.fn(delay => s =>
        s.pipe(operators.delayWhen(() => observables.timer(delay)))
      );
      return operators;
    });
    

    你可以把这个 mock 放在 node_modules 文件夹旁边:

    .
    ├── __mocks__
    │   └── rxjs
    │       └── operators.js
    └── node_modules
    
    // operators.js
    
    const operators = require("rxjs/operators");
    const observables = require("rxjs");
    
    operators.delay = jest.fn(delay => s =>
      s.pipe(operators.delayWhen(() => observables.timer(delay)))
    );
    
    module.exports = operators;
    

    一个以前没有用过的测试示例并使用模拟:

    it("some test with delay()", (done: DoneFn) => {
    
        let check = false;
    
        jest.useFakeTimers();
    
        of(true)
          .pipe(delay(1000))
          .subscribe(() => (check = true));
    
        setTimeout(() => {
          expect(check).toBe(true);
          done();
        }, 2000);
    
        jest.runTimersToTime(999);
        expect(check).toBe(false);
    
        jest.runAllTimers();
      });
    

    【讨论】:

    • 这对我来说效果很好。谢谢。
    【解决方案3】:

    从6.2.1版本开始,RxJS支持Jest的假时间,所以你可以简单写

    jest.useFakeTimers('modern');
    
    test('...', () => {
      // ...code that subscribes to your observable.
    
      jest.runAllTimers();
    
      // ...code that makes assertions.
    });
    

    另外,我发布了一个library,它使编写这样的测试变得更容易,这是一个示例(log 将日志记录添加到 observable,getMessages 检索记录的消息):

    import { getMessages, log } from '1log';
    import { of } from 'rxjs';
    import { delay } from 'rxjs/operators';
    
    test('delay', () => {
      of(42).pipe(delay(500), log).subscribe();
      jest.runAllTimers();
      expect(getMessages()).toMatchInlineSnapshot(`
        [create 1] +0ms [Observable]
        [create 1] [subscribe 1] +0ms [Subscriber]
        [create 1] [subscribe 1] [next] +500ms 42
        [create 1] [subscribe 1] [complete] +0ms
        · [create 1] [subscribe 1] [unsubscribe] +0ms
      `);
    });
    

    【讨论】:

      【解决方案4】:

      我们正在使用来自 Rxjs 的 Schedulers。

      类看起来像这样:

      import { Observable, Scheduler, Subject, asapScheduler } from 'rxjs';
      
      // ...
      
      constructor(
          @Optional() private readonly _scheduler: Scheduler
      ) {
          if (isNullOrUndefined(_scheduler)) {
              this._scheduler = asapScheduler;
          }
      }
      
      // ...
      
      this._someObservable.pipe(delay(1, this._scheduler));
      

      然后,在规范文件中提供一个模拟的TestModuleMetadata

      {
          declarations: [YourComponent],
          imports: [],
          providers: [
              { provide: Scheduler, useValue: new VirtualTimeScheduler() },
          ],
      };
      

      现在您需要做的就是在 beforeEach 块中分配调度程序,并在您希望“跳过”延迟时刷新它:

      let schedulerMock = Testbed.get(Scheduler);
      
      // ...
      
      it('should emit true', () => {
          let result: boolean = null;
          comp.someObservable.subscribe(next => (result = next));
          schedulerMock.flush();
      
          expect(result).toBe(true);
      });
      

      这也适用于其他时间相关运算符,例如 bufferTime。 您想在组件中使用或需要使用哪个调度器应该取决于您的用例,最好是查阅文档并找出最适合您的调度器。

      【讨论】:

        猜你喜欢
        • 2021-09-14
        • 2020-04-21
        • 2019-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-04
        • 2019-08-17
        相关资源
        最近更新 更多