Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can you add usePromisePolyfill() or an option to useFakeTimers's config? #1529

Closed
eMuonTau opened this issue Aug 12, 2017 · 3 comments
Closed

Comments

@eMuonTau
Copy link

As @ropez stated in his issue 738, replacing global Promise with polyfill solves the issue with native promises when using fake timers. But we should be able to restore it after tests are finished.

This is how i solve the problem.

const promisePolyfill = require('promise-polyfill');
const sinon = require("sinon");

suite('Playground', function () {
  const promiseBackup = global.Promise;
  let clock;

  setup(() => {
    global.Promise = promisePolyfill;
    clock = sinon.useFakeTimers(10000);
  });

  teardown(() => {
    clock.restore();
    global.Promise = promiseBackup;
  });

  test('Test', () => {});
});

How about something like this?

const sinon = require("sinon");

suite('Playground', function () {
  let clock;

  setup(() => {
    clock = sinon.useFakeTimers({
      now: 10000,
      usePromisePolyfill: true
    });
  });

  teardown(() => {
    clock.restore();
  });

  test('Test', () => {});
});
@fatso83
Copy link
Contributor

fatso83 commented Aug 12, 2017

Have you checked out usingPromiseLibrary? See if that resolves your issue.

@fatso83 fatso83 closed this as completed Aug 12, 2017
@eMuonTau
Copy link
Author

eMuonTau commented Aug 12, 2017

I am not sure i am doing this the right way but here is what i get. What i understand usingPromise() only works on stub methods like resolves and rejects. This example works for my case but i don't get how we can use this in complex scenarios. The problem is, when we call clock.tick() it does not wait for promises to resolve because native promise does not use timers. Should we stub everything that uses promises instead of replacing global Promise temporarily? I just started using sinon and sorry if i am asking something silly.

DirectoryWatcher.ts

import * as path from 'path';
import { EventEmitter } from 'events';
import { pathExists } from 'fs-extra';

export class DirectoryWatcher extends EventEmitter {
  private timerId: NodeJS.Timer;

  constructor(private interval: number = 2000) {
    super();
  }

  public start() {
    if (this.timerId) return;
    this.set();
  }

  public stop() {
    if (this.timerId) {
      clearTimeout(this.timerId);
    }
  }

  private watchAction() {
    pathExists('').then(exists => {
      this.emit('change', exists);
    }, error => {
      this.emit('error', error);
    }).then(() => {
      this.set();
    });
  }

  private set() {
    this.timerId = setTimeout(() => this.watchAction(), this.interval);
  }
}

DirectoryWatcher.test.ts

import { expect, use as chaiUse } from 'chai';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import * as fs from 'fs-extra';
chaiUse(sinonChai);

const promisePolyfill = require('promise-polyfill');

import { DirectoryWatcher } from '../src/DirectoryWatcher';

suite('With usingPromise()', function () {
  let watcher: DirectoryWatcher;
  let clock: sinon.SinonFakeTimers;
  let callback: sinon.SinonSpy;
  let pathExistsStub: sinon.SinonStub;
  let watcherStub: sinon.SinonStub;

  setup(() => {
    clock = sinon.useFakeTimers(10000);
    watcher = new DirectoryWatcher(2000);
    callback = sinon.spy();
    watcher.on('change', callback);
  });

  teardown(() => {
    watcher.stop();
    watcher.removeAllListeners('change');
    clock.restore();
    pathExistsStub.restore();
  });

  test('Should find directory', () => {
    stubPathExists(true);
    watcher.start();
    clock.tick(6500);
    expect(callback).to.be.calledThrice;
    expect(callback).to.be.calledWith(true);
  });

  test('Should not find directory', () => {
    stubPathExists(false);
    watcher.start();
    clock.tick(6500);
    expect(callback).to.be.calledThrice;
    expect(callback).to.be.calledWith(false);
  });

  function stubPathExists(result: boolean) {
    if (pathExistsStub) {
      pathExistsStub.restore();
    }
    pathExistsStub = sinon.stub(fs, 'pathExists').usingPromise(promisePolyfill).resolves(result);
  }
});

@fatso83
Copy link
Contributor

fatso83 commented Aug 12, 2017

Thanks for elaborating. Unfortunately you just opened a can of worms 😁 The core problem is how to deal with asynchronous bits in the synchronous world of Lolex (which is responsible for Sinon's fake timers).

Progress on how to deal with promises has stalled on the Lolex project, so we have no official path going forward. For an interesting discussion see sinonjs/fake-timers#105.

For the moment you can hack around the issue. Save a reference to the original setTimeout and call that in between timer ticks to resolve promises.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants