クリックによって呼び出される関数で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!');
});
});
テストを終了する前に、どういうわけか約束が満たされるのを待つ必要はほとんどありません。私が見ることができるあなたのコードからそれを行うには2つの主な方法があります。
onClick
とpromiseメソッドを個別にテストします。 onClick
が正しい関数を呼び出していることを確認してください。ただし、setTextWithPromise
をスパイし、クリックをトリガーしてsetTextWithPromise
が呼び出されたことをアサートします。次に、コンポーネントインスタンスを取得して、ハンドラーをアタッチし、それが正しいことをアサートできるというpromiseを返すそのメソッドを呼び出すこともできます。
渡すことができるコールバックプロップを公開します。これは、promiseの解決時に呼び出されます。
更新された回答:async
/await
を使用すると、コードがより簡潔になります。以下の古いコード。
次の要素を組み合わせることで、この問題を解決しました。
async
をマークして、テストを非同期にしますあなたの例では、それは次のようになります:
_// 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つ以上のプロミスを待つ必要がある場合は、大きなネストされたコールバックが発生するため、これは良い方法ではありません。