web-dev-qa-db-ja.com

シミュレートされたクリックがPromiseを呼び出す関数を呼び出すときのReactのJestおよびEnzymeによるテスト

  • React v15.1.0
  • Jest v12.1.1
  • 酵素v2.3.0

クリックによって呼び出される関数でpromiseを呼び出すコンポーネントをテストする方法を理解しようとしています。私はJestのrunAllTicks()関数が私を助けることを期待していましたが、それは約束を実行しているようではありません。

成分:

import React from 'react';
import Promise from 'bluebird';

function doSomethingWithAPromise() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, 50);
  });
}

export default class AsyncTest extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      promiseText: '',
      timeoutText: ''
    };

    this.setTextWithPromise = this.setTextWithPromise.bind(this);
    this.setTextWithTimeout = this.setTextWithTimeout.bind(this);
  }

  setTextWithPromise() {
    return doSomethingWithAPromise()
      .then(() => {
        this.setState({ promiseText: 'there is text!' });
      });
  }

  setTextWithTimeout() {
    setTimeout(() => {
      this.setState({ timeoutText: 'there is text!' });
    }, 50);
  }

  render() {
    return (
      <div>
        <div id="promiseText">{this.state.promiseText}</div>
        <button id="promiseBtn" onClick={this.setTextWithPromise}>Promise</button>
        <div id="timeoutText">{this.state.timeoutText}</div>
        <button id="timeoutBtn" onClick={this.setTextWithTimeout}>Timeout</button>
      </div>
    );
  }
}

そしてテスト:

import AsyncTest from '../async';
import { shallow } from 'enzyme';
import React from 'react';

jest.unmock('../async');

describe('async-test.js', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallow(<AsyncTest />);
  });

  // FAIL
  it('displays the promise text after click of the button', () => {
    wrapper.find('#promiseBtn').simulate('click');

    jest.runAllTicks();
    jest.runAllTimers();

    wrapper.update();

    expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
  });

  // PASS
  it('displays the timeout text after click of the button', () => {
    wrapper.find('#timeoutBtn').simulate('click');

    jest.runAllTimers();

    wrapper.update();

    expect(wrapper.find('#timeoutText').text()).toEqual('there is text!');
  });
});
13
Caspar

テストを終了する前に、どういうわけか約束が満たされるのを待つ必要はほとんどありません。私が見ることができるあなたのコードからそれを行うには2つの主な方法があります。

  1. onClickとpromiseメソッドを個別にテストします。 onClickが正しい関数を呼び出していることを確認してください。ただし、setTextWithPromiseをスパイし、クリックをトリガーしてsetTextWithPromiseが呼び出されたことをアサートします。次に、コンポーネントインスタンスを取得して、ハンドラーをアタッチし、それが正しいことをアサートできるというpromiseを返すそのメソッドを呼び出すこともできます。

  2. 渡すことができるコールバックプロップを公開します。これは、promiseの解決時に呼び出されます。

1
monastic-panic

更新された回答:async/awaitを使用すると、コードがより簡潔になります。以下の古いコード。

次の要素を組み合わせることで、この問題を解決しました。

  • 約束を模倣し、すぐに解決する
  • テスト関数asyncをマークして、テストを非同期にします
  • クリックをシミュレートした後、次の macrotask まで待機して、promiseに解決する時間を与えます

あなたの例では、それは次のようになります:

_// Mock the promise we're testing
global.doSomethingWithAPromise = () => Promise.resolve();

// Note that our test is an async function
it('displays the promise text after click of the button', async () => {
    wrapper.find('#promiseBtn').simulate('click');
    await tick();
    expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
});

// Helper function returns a promise that resolves after all other promise mocks,
// even if they are chained like Promise.resolve().then(...)
// Technically: this is designed to resolve on the next macrotask
function tick() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  })
}
_

Enzymeのupdate()は、この方法を使用する場合、十分ではなく、必要もありません。Promiseは、設計上、作成された同じティックで解決されないためです。ここで何が起こっているかの非常に詳細な説明については、 この質問 を参照してください。

元の答え:同じロジックですが、少しきれいではありません。 Nodeの setImmediate を使用して、次のティック、つまりプロミスが解決されるまでテストを延期します。次に、Jestの done を呼び出して、テストを非同期で終了します。

_global.doSomethingWithAPromise = () => Promise.resolve({});

it('displays the promise text after click of the button', (done) => {
    wrapper.find('#promiseBtn').simulate('click');

  setImmediate( () => {
    expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
    done();
  })
});
_

2つ以上のプロミスを待つ必要がある場合は、大きなネストされたコールバックが発生するため、これは良い方法ではありません。

39
Jonathan Stray