J'essaie de comprendre comment tester un composant qui appelle une promesse dans une fonction invoquée par un clic. Je m'attendais à ce que la fonction runAllTicks()
de Jest m'aide ici, mais elle ne semble pas tenir sa promesse.
Composant:
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>
);
}
}
Et les tests:
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!');
});
});
Il n'y a pas grand-chose à attendre d'une manière ou d'une autre que la promesse se réalise avant de terminer le test. Il y a deux façons principales de le faire à partir de votre code que je peux voir.
tester indépendamment que onClick
et vos méthodes de promesse. Vérifiez donc que onClick
appelle la fonction correcte, mais espionnez setTextWithPromise
, déclenchant un clic et affirmant que setTextWithPromise
a été appelé. Ensuite, vous pouvez également obtenir l'instance du composant et appeler cette méthode qui renvoie la promesse que vous pouvez attacher un gestionnaire et affirmer qu'il a fait la bonne chose.
exposer un accessoire de rappel que vous pouvez transmettre qui est appelé lorsque la promesse se résout.
Réponse mise à jour: en utilisant async
/await
conduit à un code plus propre. Ancien code ci-dessous.
J'ai réussi à résoudre ce problème en combinant les éléments suivants:
async
Dans votre exemple, cela pourrait ressembler à ceci:
// 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);
})
}
La update()
d'Enzyme n'est ni suffisante ni nécessaire lors de l'utilisation de cette méthode, car les promesses ne se résolvent jamais dans le même tick qu'elles ont été créées - par conception. Pour une explication très détaillée de ce qui se passe ici, voir cette question .
Réponse originale: même logique mais légèrement moins jolie. Utilisez setImmediate
de Node pour différer le test jusqu'au prochain tick, c'est-à-dire lorsque la promesse se résoudra. Appelez ensuite Jest's done
pour terminer le test de manière asynchrone.
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();
})
});
Ce n'est pas aussi agréable, car vous obtiendrez de gros rappels imbriqués si vous devez attendre plus d'une promesse.