Le cadre 
redux-saga fournit un tas de modèles intéressants pour travailler avec les effets secondaires, mais, comme les vrais développeurs d'entreprise sanglants, nous devons couvrir l'ensemble de notre code avec des tests. Voyons comment nous allons tester nos sagas.

Prenons l'exemple du clicker le plus simple. Le flux de données et la signification de l'application seront les suivants:
- L'utilisateur enfonce un bouton.
- Une demande est envoyée au serveur, informant que l'utilisateur a piqué un bouton.
- Le serveur renvoie le nombre de clics effectués.
- L'état enregistre le nombre de clics effectués.
- L'interface utilisateur est mise à jour et l'utilisateur voit que le nombre de clics a augmenté.
- ...
- PROFIT.
Dans notre travail, nous utilisons Typescript, donc tous les exemples seront dans cette langue.
Comme vous l'avez probablement déjà deviné, nous implémenterons tout cela avec 
redux-saga . Voici le code de l'ensemble du fichier sagas:
 export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) } 
Dans cet exemple simple, nous déclarons la saga 
processClick , qui traite directement l'action et la saga 
watchClick , qui crée une boucle pour le traitement des 
action' .
Générateurs
Nous avons donc la saga la plus simple. Il envoie une requête au serveur 
( call) , reçoit le résultat et le transmet au réducteur 
( put) . Nous devons en quelque sorte tester si la saga transmet exactement ce qu'elle reçoit du serveur. Commençons.
Pour les tests, nous devons verrouiller l'appel du serveur et vérifier en quelque sorte si exactement ce qui provenait du serveur est entré dans le réducteur.
Étant donné que les sagas sont des fonctions de générateur, la méthode 
next() , qui se trouve dans le prototype de générateur, sera le moyen le plus évident de tester. Lorsque vous utilisez cette méthode, nous avons à la fois la possibilité de recevoir la prochaine valeur du générateur et de transférer la valeur au générateur. Ainsi, nous sortons de la boîte la possibilité de recevoir des appels mouillés. Mais tout est-il si rose? Voici un test que j'ai écrit sur des générateurs nus:
 it('should increment click counter (behaviour test)', () => { const saga = processClick() expect(saga.next().value).toEqual(call(ServerApi.SendClick)) expect(saga.next(10).value).toEqual(put(Actions.clickSuccess(10))) }) 
Le test était concis, mais que teste-t-il? En fait, il répète simplement le code de la méthode de la saga, c'est-à-dire qu'avec tout changement dans la saga, le test devra être changé.
Un tel test n'aide pas au développement.
Plan de test de Redux-saga
Après avoir rencontré ce problème, nous avons décidé de le rechercher sur Google et avons soudainement réalisé que nous n'étions pas les seuls et loin du premier. Directement 
dans la documentation de 
redux-saga développeurs proposent un aperçu de plusieurs bibliothèques créées spécifiquement pour satisfaire les fans de tests.
De la liste proposée, nous avons pris la bibliothèque 
redux-saga-test-plan . Voici le code de la première version du test que j'ai écrit avec:
 it('should increment click counter (behaviour test with test-plan)', () => { return expectSaga(processClick) .provide([ call(ServerApi.SendClick), 2] ]) .dispatch(Actions.click()) .call(ServerApi.SendClick) .put(Actions.clickSuccess(2)) .run() }) 
Le constructeur de test dans 
redux-saga-test-plan est la fonction 
expectSaga , qui renvoie l'interface qui décrit le test. La saga de test ( 
processClick de la première liste) est passée à la fonction elle-même.
À l'aide de la méthode 
provide , vous pouvez bloquer les appels de serveur ou d'autres dépendances. Un tableau de 
StaticProvider' , qui décrit quelle méthode doit retourner.
Dans le bloc 
Act , nous avons une seule méthode - l' 
dispatch . Une action lui est transmise, à laquelle la saga va répondre.
Le bloc 
assert compose des méthodes 
call put , qui vérifient si les effets correspondants ont été causés pendant le travail de la saga.
Tout se termine avec la méthode 
run() . Cette méthode exécute le test directement.
Les avantages de cette approche:
- Il vérifie si la méthode a été appelée et non la séquence d'appels;
- moki décrit clairement quelle fonction est mouillée et ce qui revient.
Cependant, il y a du travail à faire:
- il y a plus de code;
- le test est difficile à lire;
- il s'agit d'un test de comportement, ce qui signifie qu'il est toujours lié à la mise en œuvre de la saga.
Les deux derniers coups
Test de condition
Tout d'abord, nous corrigeons le dernier: nous faisons un test d'état à partir d'un test de comportement. Le fait que 
test-plan vous permette de définir l' 
state initial et de passer le 
reducer , qui devrait répondre aux effets de 
put générés par la saga, nous y aidera. Cela ressemble à ceci:
 it('should increment click counter (state test with test-plan)', () => { const initialState = { clickCount: 11, return expectSaga(processClick) .provide([ call(ServerApi.SendClick), 14] ]) .withReducer(rootReducer, initialState) .dispatch(Actions.click()) .run() .then(result => expect(result.storeState.clickCount).toBe(14)) }) 
Dans ce test, nous ne vérifions plus que des effets ont été déclenchés. Nous vérifions l'état final après l'exécution, et ça va.
Nous avons réussi à nous débarrasser de l'implémentation de la saga, essayons maintenant de rendre le test plus compréhensible. C'est facile si vous remplacez 
then() par 
async/await :
 it('should increment click counter (state test with test-plan async-way)', async () => { const initialState = { clickCount: 11, } const saga = expectSaga(processClick) .provide([ call(ServerApi.SendClick), 14] ]) .withReducer(rootReducer, initialState) const result = await saga.dispatch(Actions.click()).run() expect(result.storeState.clickCount).toBe(14) }) 
Tests d'intégration
Mais que se passe-t-il si nous obtenons également une opération de clic inversé (appelons-la unclick), et maintenant notre fichier sag ressemble à ceci:
 export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* processUnclick() { const result = yield call(ServerApi.SendUnclick) yield put(Actions.clickSuccess(result)) } function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) } function* watchUnclick() { yield takeEvery(ActionTypes.UNCLICK, processUnclick) } export default function* mainSaga() { yield all([watchClick(), watchUnclick()]) } 
Supposons que nous devions tester que lorsque les actions de clic et de déclic sont appelées dans l'état, le résultat du dernier trajet vers le serveur est écrit dans l'état. Un tel test peut également être facilement effectué avec 
redux-saga-test-plan :
 it('should change click counter (integration test)', async () => { const initialState = { clickCount: 11, } const saga = expectSaga(mainSaga) .provide([ call(ServerApi.SendClick), 14], call(ServerApi.SendUnclick), 18] ]) .withReducer(rootReducer, initialState) const result = await saga .dispatch(Actions.click()) .dispatch(Actions.unclick()) .run() expect(result.storeState.clickCount).toBe(18) }) 
Veuillez noter que nous testons 
mainSaga , et non des gestionnaires d' 
mainSaga individuels.
Cependant, si nous exécutons ce test tel quel, nous obtiendrons le Vorning:

Cela est dû à l'effet 
takeEvery - il s'agit d'une boucle de traitement des messages qui fonctionnera pendant que notre application est ouverte. En conséquence, le test dans lequel 
takeEvery est appelé ne pourra pas terminer le travail sans aide extérieure, et 
redux-saga-test-plan met fin de force à ces effets après 250 ms après le début du test. Ce délai peut être modifié en appelant expectSaga.DEFAULT_TIMEOUT = 50.
Si vous ne souhaitez pas recevoir de tels vorings, un pour chaque test avec un effet complexe, utilisez simplement la méthode silentRun() au lieu de la méthode run() .
Pièges
Où sans écueils ... Au moment d'écrire ces lignes, la dernière version de redux-saga: 1.0.2. Dans le même temps, 
redux-saga-test-plan ne peut fonctionner qu'avec JS.
Si vous voulez TypeScript, vous devez installer la version à partir du canal bêta:
npm install redux-saga-test-plan@betaet désactivez les tests de la version. Pour ce faire, dans le fichier tsconfig.json, vous devez spécifier le chemin "./src/**/*.spec.ts" dans le champ "exclure".
Malgré cela, nous considérons que 
redux-saga-test-plan la meilleure bibliothèque pour tester 
redux-saga . Si vous avez 
redux-saga dans votre projet, ce sera peut-être un bon choix pour vous.
Le code source de l'exemple sur 
GitHub .