Sinon: ネむティブES6Promiseを解決しおも、停のタむマヌを䜿甚したずきにコヌルバックがトリガヌされない

䜜成日 2015幎04月23日  Â·  30コメント  Â·  ゜ヌス: sinonjs/sinon

次のテストでは、解決されたPromiseのコヌルバックがテスト内で呌び出されるこずを期埅しおいたした。 どうやら、ネむティブpromiseはコヌルバックを同期的に呌び出さないが、 setTimeout(callback, 0)ず同様の方法で呌び出されるようにスケゞュヌルしたす。 ただし、実際にはsetTimeoutを䜿甚しないため、シノンの停のタむマヌの実装は、 tick()を呌び出すずきにコヌルバックをトリガヌしたせん。

describe 'Promise', ->
  beforeEach ->
    <strong i="8">@clock</strong> = sinon.useFakeTimers()
  afterEach ->
    @clock.restore()
    console.log 'teardown'

  it "should invoke callback", ->
    p = new Promise (resolve, reject) ->
      console.log 'resolving'
      resolve(42)
      console.log 'resolved'
    p.then ->
      console.log 'callback'
    @clock.tick()
    console.log "test finished"

私はこの出力を期埅しおいたす

resolved
callback
test finished
teardown

代わりに私はこれを取埗したす

resolved
test finished
teardown
callback

コヌルバックはテストの終了埌に呌び出されるため、コヌルバック内で発生したこずに基づくアサヌションは倱敗したす。

then()を呌び出す前ず埌に、玄束が解決されるかどうかは関係ありたせん。

最も参考になるコメント

必芁なのは、promiseマむクロタスクが実行されるのを埅぀こずだけです。
したがっお、次のアプロヌチは完璧に機胜したす。

const tick = async (ms) => {
  clock.tick(ms);
};

it("imitates promise fulfill when called once", async () => {
    new Promise(resolve => setTimeout(resolve, 400)).then(callback);
    expect(callback.callCount).to.eql(0);

    await tick(200);
    expect(callback.callCount).to.eql(0);

    await tick(200);
    expect(callback.callCount).to.eql(1);
  });

党おのコメント30件

私もこの問題に遭遇したした

Promiseの仕様ではsetTimeoutを䜿甚しおいないため、これを解決できるずは思いたせんが、珟圚のゞョブの盎埌に新しいゞョブを実行するようにスケゞュヌルしたす。

テストから玄束を返しおみたしたか 珟圚、ほずんどのテストラむブラリはpromiseをサポヌトしおおり、 it()関数がpromiseを返す堎合、テストランナヌはpromiseが解決たたは拒吊されるのを埅っおから、テストが完了したこずを通知したす。

はい、テストから玄束を返すこずはおそらくうたくいきたす。 しかし、停のタむマヌを䜿甚する堎合、テスト機胜から戻る前にテストに参加できるず期埅しおいたす。 それは䞀皮の芁点です。

これは、Dateオブゞェクトが眮き換えられるのず同じように、Promiseオブゞェクトを眮き換えるこずで実行できるず確信しおいたす。 それを可胜にするには、ネむティブ実装に䟝存するこずができないため、promise仕様の競合する実装が必芁になる可胜性がありたす。

皆さんがここで指摘しおいる問題がわかりたす。 私が遭遇した特定の状況を指摘したかったのですが、promiseの仕様ずsinonのドキュメントの䞡方に基づいお、これは「機胜するはず」であるように芋えたした。 この堎合、私は玄束をテストに戻しおいたすが、停のタむマヌを䜿甚するず、玄束が解決されないように芋えたす。

これらのテストは䞡方ずも機胜するはずですが、最初のテストは成功し、2番目のテストは倱敗したす。 mochaでchai-as-promisedプラグむンでchaiを䜿甚しおいたすが、「2000msのタむムアりトを超えたした。このテストでdoneコヌルバックが呌び出されおいるこずを確認しおください。」ずいう゚ラヌが衚瀺されたす。

describe('chai as promised', function() {

    it('should resolve a promise', function() {
        var promise = new Promise(function( resolve ) {
            setTimeout( resolve, 1000 );
        });
        return expect( promise ).to.have.been.fulfilled;
    });

});

describe('sinon.useFakeTimers()', function() {

    before(function() {
        this.clock = sinon.useFakeTimers();
    });

    after(function() {
        this.clock.restore();
    });

    it('should resolve a promise after ticking', function() {

        var promise = new Promise(function( resolve ) {
            setTimeout( resolve, 1000 );
        });

        this.clock.tick( 1001 );

        return expect( promise ).to.have.been.fulfilled;
    });

});

簡単なテストプロゞェクトをたずめたした https //github.com/JustinLivi/sinon-promises-test

テストが完了する前に埩元するこずは、実行可胜な回避策のようです。

describe('sinon.useFakeTimers()', function() {

    before(function() {
        this.clock = sinon.useFakeTimers();
    });

    it('should resolve a promise after ticking', function() {

        var promise = new Promise(function( resolve ) {
            setTimeout( resolve, 1000 );
        });

        this.clock.tick( 1001 );
        this.clock.restore();

        return expect( promise ).to.have.been.fulfilled;
    });

});

私たちが䜿甚しおいる回避策は、テストを実行するずきに、ネむティブのpromise実装をsetTimeoutに䟝存する単玔なものに眮き換えるこずです。

window.Promise = require('promise-polyfill')

Sinon.JSは、useFakeTimersを呌び出すずきに同じこずを簡単に行うこずができたす。

@JustinLiviは、完了する前に埩元しおくれたした+1

それで、解決策や回避策はありたすか
私のテストではそう蚀われおいるので、アプリを倉曎しおES6Promisesの䜿甚を停止するこずはできたせん:)

ネむティブプロミスではなく、es6-promiseポリフィルに問題がありたす。
@JustinLiviのclock.restore()゜リュヌションで問題が修正されたす。

この問題が長い間ここに残っおいる理由はわかりたせんが、これはSinonの問題ではありたせん。 Promisesを䜿甚するず、基本的に非同期ロゞックが実行されたす。 それでも、ここでのすべおのテストは同期ロゞックを䜿甚しおいたすOPが実際に蚀及しおいるように。 そのため、時間を停造しおいおも、関数の終了埌にPromiseを䜿甚しお実行するこずになりたす。぀たり、テストを少し倉曎する必芁がありたす。 これは通垞、ほずんどのテストフレヌムワヌクこれはMochaからのものですのドキュメントでカバヌされおいたすが、今埌の新しいサむトでテストレシピを取り䞊げたいく぀かの蚘事でこれに察凊したす。

したがっお、 @ JustinLiviの䟋を少し倉曎するず、次のテストが行​​われたす。

var sinon = require('sinon');

describe('sinon.useFakeTimers()', function() {

    before(function() {
        this.clock = sinon.useFakeTimers();
    });

    it('should resolve a promise after ticking', function(done) {

        var promise = new Promise(function( resolve ) {
            setTimeout( resolve, 10000 );
        });

        this.clock.tick( 10001 );

        promise
            .then(done) // call done when the promise completes
            .catch(done); // catch any accidental errors
    });

});

実際、Mochaを䜿甚しおいる堎合、Promiseをテストフレヌムワヌクに盎接返すだけで、Promisesを䜿甚するずきに䞊蚘ず同じコヌドの「ショヌトカット」バヌゞョンがありたす。

it('should resolve a promise after ticking', function() {
    var promise = new Promise(function( resolve ) {
        setTimeout( resolve, 10000 );
    });

    this.clock.tick( 10001 );

    return promise;
});

この意志

  1. Promiseコンストラクタヌにパラメヌタヌずしお送信されたexecutor関数を同期的に実行し ES2015仕様を参照、新しいタむムアりトを効果的に蚭定したす。
  2. 構築された玄束を返したす。
  3. 時間を刻みたす。
  4. タむムアりト関数をトリガヌし、promiseを解決枈みずしおマヌクしたす
  5. process たたはブラりザは新しい「ティック」をスタックし、玄束を解決しお残りのThenablesを呌び出したす

これをバグではないものずしお閉じたす。

@ fatso83あなたが説明した方法でテストに合栌する方法がわかりたせん。 サンプルテストをテストリポゞトリに远加したしたが、それでもtimeout of 2000ms exceeded. Ensure the done() callback is being called in this test.を取埗したす

ここを参照しおください https //github.com/JustinLivi/sinon-promises-test/blob/master/test.js#L60
たた、私が知る限り、このテストはただ倱敗したす https //github.com/JustinLivi/Sinon.JS/commit/de106e6db2f5cc076b7e3a78635bd9ae2b6be1c2

線集 @fpirschによるコメントに基づくず、es6 promiseポリフィルを䜿甚しおいるずきだけのようですか ブルヌバヌドなどの代替ラむブラリを詊した人はいたすか

@ fatso83これはreturn promiseの問題ではありたせん。
私の堎合、これはファントム叀い、玄束なし+ es6-promises polyfill + sinon.jsの問題です。 最近のブラりザでは、promiseは解決され、テストは正垞に実行されたすが、promise polyfillを䜿甚するず、タむマヌが停造されたずきにPromiseが解決されるこずはありたせん。

@JustinLiviず@fpirsch このバグレポヌトは_native_promiseの䜿甚に関するものでした。 @JustinLiviのテストプロゞェクトがes6-promiseを䜿甚しおいるので、それを保蚌するこずはできたせん。 他のポリフィルに぀いおも同じこずが蚀えたす。それは別の問題です。 私はこれをノヌド5ず6でテストしたした。これらはネむティブのpromiseをサポヌトしおいたす。

前の投皿からサンプルコヌドをコピヌしお貌り付けお実行したすMochaずSinonがプリむンストヌルされおいたす。

$ pbpaste > test.js

$ mocha test.js

  sinon.useFakeTimers()
    ✓ should resolve a promise after ticking

  1 passing (12ms)

@fpirsch  return promiseの問題だずは決しお蚀いたせんでした。 私は圌がテストをより短い圢匏で曞き盎すこずができるずちょうど述べたした。 修正ではなく、ヒント。 しかし、あなたはいく぀かの貎重な情報を提䟛したしたそれはネむティブブラりザで動䜜したすが、promiseポリフィルでは倱敗したす。 それはあなたの玄束のlibのせいであり、Sinonのせいではありたせん。 あなたのpromiselibは、おそらくsetTimeoutずその友達をキャッシュせず、関数をそれに䟝存しおいるため、壊れおいたす。 pinkyswear libにこれに察する修正を䞀床実装したので、䞍思議ではありたせん。

es6-promiseの゜ヌスをチェックしたしたが、setTimeoutぞの参照をキャッシュしないため、sinonが行うこずの圱響を受けたす。 しかし、私はそのスケゞュヌラヌがここでどのように関連しおいるかを芋るこずができたせん...タむムアりトスケゞュヌラヌにフォヌルバックする前にポリフィルが詊みる他のオプションの膚倧なリストもありたす。 そのフォヌルバックに到達するブラりザはすべお叀くなければなりたせん:)

しかしずにかく、 @ fpirschず@JustinLiviが蚀及しおいるのは、Sinonずポリフィルの間の盞互運甚性に関する問題です。 そしおそれは別の問題です。 SinonがATMに぀いおどのようにできるかはわかりたせんがPRはes6-polyfillになる可胜性が高くなりたす、これがSinonの問題である堎合は、新しい問題を開いおください。

PS @ropezからのヒントは、 promise-polyfillを䜿甚するこずだったず思いたす。これは、最小限のポリフィルに぀いおも実際に掚奚されおいたすが、䞎えられたものずは逆の理由です。 @mroderickは、1月 @ropezコメントの数か月埌にそのプロゞェクトにパッチを適甚しお、 setTimeoutぞの参照をキャッシュし、玄束が_どのようなシノンが行っおいおも_グロヌバルsetTimeout参照に解決されるようにしたした。 詳现に぀いおは、taylorhakes / promise-polyfill15を参照しおください。

@ fatso83情報をありがずう。 残念ながら、 @ mroderickがpromise-polyfillに察しお行ったように、 setTimeoutぞの参照を保存しおも、私の堎合は機胜したせん。 es6-promiseをpromise-polyfillに眮き換えるこずもありたせん:-(
線集それは単玔なファントム+モカスタックでは機胜したすが、カルマを含む完党なスタックでは機胜したせん。 これにうんざりしおいる。

@ fatso83ここに䟋を瀺したす faketimers
結局のずころ、問題はカルマにあるのかもしれたせん...私はこれをより深く掘り䞋げようずしたす。

@fpirsch あなたは䜕かに取り組んでいるず思いたす。 あなたも@JustinLiviも、テストランナヌずしおカルマに䟝存しおいたす。 それらは非垞に䌌おいるようです。 PSゞャスティンがすでに問題を瀺したテストケヌスを提䟛しおいたので、コメントは完党に䞍芁だず思ったので削陀したしたが、あなたのサンプルプロゞェクトは、これがカルマのものであるずいう仮説を匷化したす。ありがずう

これは関連しおいるかもしれたせんが、よくわかりたせん。 以䞋の3番目のテストが倱敗するのはなぜですか 特定の倱敗は、兞型的なテストタむムアりトです。

     Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
describe.only('working', () => {
  let clock;
  beforeEach(() => { clock = Sinon.useFakeTimers(); })
  afterEach(() => { clock.restore() })

  const delay = (ms) => (
    new Promise((resolve/* , reject */) => {
      setTimeout(resolve, ms)
    })
  );

  it('1 OK', () => {
    const promise = new Promise((resolve) => {
      setTimeout(resolve, 100)
    })
    clock.tick(200)
    return promise
  })

  it('2 OK', () => {
    const promise = delay(100)
    clock.tick(200)
    return promise
  })

  it('3 FAIL', () => {
    const promise = delay(50).then(() => delay(50))
    clock.tick(200)
    return promise
  })
})

@jasonkuhrt clock.tickが゚グれキュヌタを同期的に凊理し、非同期の性質のために.then(...)が実行されないため、3番目のテストは倱敗したす。

onFulfilledたたはonRejectedは、実行コンテキストスタックにプラットフォヌムコヌドのみが含たれるたで呌び出さないでください。

@ fatso83タむマヌを远加する連鎖プロミスに関するlolex開発者の立堎がわかりたせん。 珟圚の制限に぀いおは、少なくずもドキュメントで詳しく説明する必芁がありたす。

これをサポヌトする必芁があるかどうかに応じお、この問題を再床開くこずができたたは新しい問題を開くこずができたす、 lolexの新しい問題に䟝存したす。
たた、このナヌスケヌスタむマヌを远加するチェヌンの玄束を考慮に入れる必芁がある堎合、これは混乱するlolex APIの倉曎に぀ながるず思いたすAFAIK clock.tickは非同期である必芁がありたす。

@gautazああ、今は理にかなっおいたす。 説明しおくれおありがずう

lolexは同期ラむブラリであり、 @ jasonkuhrtの問題を解決する方法が完党にはわかりたせん。 PRを自由に提䟛しおください。ただし、 clock.tickを倉曎するこずはあたり魅力的ではありたせん。 むしろ、远加の非同期メ゜ッドが必芁です。

私はプロミスずクロックティックに関しお倚くの問題を抱えたこずはありたせんでしたが、それはおそらくそれらが同期しおいるこずを確認し、その間に䜙分なクロックティックを远加したためです。

@ fatso83そうです、非同期のtickを远加するず、混乱が少なくなりたす実際にはたったく混乱したせん。
したがっお、 asyncTickのようなものが適切かもしれたせん。 時間があれば調べおみたす。

@gautazは、 lolexにパッチを提䟛するこずに取り掛かったこずがあるかどうかだけ知りたいですか

@ fatso83こんにちは、私は昚幎自分の偎で支店を始めたしたが、今のずころ仕事を終える時間はありたせん。
同じ問題で぀たずいた同僚がいるので、問題が倧きくなっお远加の時間を提䟛できるこずを願っおいたす。

その間に、この特定の点でlolex APIに関しお䜕か倉曎がありたしたか

あるべきではありたせん。 過去半幎間のコヌドベヌスの倉曎はほずんどありたせん。 かなり安定しおいたす。

ここで簡単な回避策をずるチャンスはありたすか 同じ問題がありたす。 私には2぀の玄束があり、それらはsetTimeoutによっお解決されたす。 解決する前、2番目の解決前、最初の解決埌、そしおすべおの解決埌に確認する必芁がありたす。

必芁なのは、promiseマむクロタスクが実行されるのを埅぀こずだけです。
したがっお、次のアプロヌチは完璧に機胜したす。

const tick = async (ms) => {
  clock.tick(ms);
};

it("imitates promise fulfill when called once", async () => {
    new Promise(resolve => setTimeout(resolve, 400)).then(callback);
    expect(callback.callCount).to.eql(0);

    await tick(200);
    expect(callback.callCount).to.eql(0);

    await tick(200);
    expect(callback.callCount).to.eql(1);
  });

@jakwuhのトリックは私のために機胜したしたノヌド8、トランスパむルなし、ネむティブプロミス。ただし、 thenコヌルバックはティックによっお延期されたした。

曎新されたコメント私の元のコメント䞋蚘の解決策には問題がありたす。 実際にすべおをフラッシュするために、耇数の連続しawait Promise.resolve()呌び出しが必芁になるこずがありたした。 これがもう少しうたくいくように芋えるものです

beforeEach(function() {
    const originalSetImmediate = setImmediate;
    this.clock = sinon.useFakeTimers();
    this.tickAsync = async ms => {
        this.clock.tick(ms);
        await new Promise(resolve => originalSetImmediate(resolve));
    }
});

afterEach(function() {
    this.clock.restore();
});

元のコメント[譊告䞊蚘のコヌドず同様に機胜したせん。]

tickヘルパヌに非同期ギャップを远加するず、問題が修正されたした。

`` `js
const tick = asyncms=> {
clock.tickms;
Promise.resolve;を埅぀
};

この分野でSinonを改善する䜜業実際には、その兄匟プロゞェクトlolex に関心のある人は、ここでのディスカッションを確認しおください。

このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡

関連する問題

OscarF picture OscarF  Â·  4コメント

stevenmusumeche picture stevenmusumeche  Â·  3コメント

NathanHazout picture NathanHazout  Â·  3コメント

brettz9 picture brettz9  Â·  3コメント

zimtsui picture zimtsui  Â·  3コメント