tutorial sagas rootsaga react example español all javascript reactjs generator redux redux-saga

javascript - sagas - redux saga tutorial



¿Cómo probar fallos de solicitud de API con Redux Saga? (3)

Estoy tratando de probar todos los escenarios que mi saga podría seguir, pero no puedo hacer que suceda lo que quiero. Esto es bastante simple, tengo una solicitud HTTP (inicio de sesión), y quiero probar el éxito y los casos de fallas burlándose de mi método API.

Pero, parece que el call effect no dispara mi función api, todavía no entiendo cómo funciona, pero supongo que el middleware está a cargo de invocar la función, y como no voy a través de Almacenar en mi prueba, no puedo obtener el resultado.

Entonces, mi pregunta es, ¿cómo puedes probar tu saga cuando necesitas enviar diferentes acciones (generalmente, éxito o fracaso) al lado de tu llamada asíncrona?

Busqué un ejemplo, encontré sagas con éxito y fracaso, pero el caso de falla nunca se prueba, por ejemplo, en el ejemplo de carrito de la compra here

SAGA.JS

export function* login(action) { try { const user = yield call(api.login, action); return yield put(actions.loginSuccess(user)); } catch(e) { yield put(actions.loginFail(e)); } } export default function* rootAuthenticationSagas() { yield* takeLatest(LOGIN, login); }

Prueba.js

describe(''login'', () => { context(''When it fails'', () => { before(''Stub the api'', () => { sinon.stub(api, ''login'', () => { // IT NEVER COMES HERE ! return Promise.reject({ error: ''user not found'' }); }); }); it(''should return a LOGIN_FAIL action'', () => { const action = { payload: { name: ''toto'', password: ''123456'' } }; const generator = login(action); // THE CALL YIELD generator.next(); const expectedResult = put({ type: ''LOGIN_FAIL'', payload: { error: ''user not found'' } }); expect(generator.next().value).to.be.eql(expectedResult); // FAIL BECAUSE I GET A LOGIN_SUCCESS INSTEAD OF A FAIL ONE }); }); });


Correcto: como lo entiendo, el punto central de Redux-Saga es que su función de saga usa las API de saga para devolver objetos que describen la acción, y luego el middleware mira esos objetos para ejecutar el comportamiento. Por lo tanto, una yield call(myApiFunction, "/someEndpoint", arg1, arg2) en una saga podría devolver un objeto que se parece a {effectType : CALL, function: myApiFunction, params: [arg1, arg2]} .

Puede inspeccionar la fuente de redux-saga para ver exactamente cómo se ven esos objetos declarativos y crear un objeto coincidente para comparar en su prueba, o usar las funciones de la API para crear los objetos (que es lo que redux-saga lo hace en su código de prueba).


También es posible que desee utilizar una biblioteca auxiliar para probar sus Sagas, como redux-saga-testing .

Descargo de responsabilidad: escribí esta biblioteca para resolver exactamente el mismo problema

Esta biblioteca hará que su prueba se vea como cualquier otra prueba (sincrónica), que es mucho más fácil de razonar que llamar a generator.next() manualmente.

Tomando su ejemplo, podría escribir las siguientes pruebas:

(Está usando la sintaxis de Jest, pero es esencialmente lo mismo con Mocha, es completamente independiente de bibliotecas de prueba)

import sagaHelper from ''redux-saga-testing''; import { call, put } from ''redux-saga/effects''; import actions from ''./my-actions''; import api from ''./your-api''; // Your example export function* login(action) { try { const user = yield call(api.login, action); return yield put(actions.loginSuccess(user)); } catch(e) { yield put(actions.loginFail(e.message)); // Just changed that from "e" to "e.message" } } describe(''When testing a Saga that throws an error'', () => { const it = sagaHelper(login({ type: ''LOGIN'', payload: ''Ludo''})); it(''should have called the API first, which will throw an exception'', result => { expect(result).toEqual(call(api, { type: ''LOGIN'', payload: ''Ludo''})); return new Error(''Something went wrong''); }); it(''and then trigger an error action with the error message'', result => { expect(result).toEqual(put(actions.loginFail(''Something went wrong''))); }); }); describe(''When testing a Saga and it works fine'', () => { const it = sagaHelper(login({ type: ''LOGIN'', payload: ''Ludo''})); it(''should have called the API first, which will return some data'', result => { expect(result).toEqual(call(api, { type: ''LOGIN'', payload: ''Ludo''})); return { username: ''Ludo'', email: ''[email protected]'' }; }); it(''and then call the success action with the data returned by the API'', result => { expect(result).toEqual(put(actions.loginSuccess({ username: ''Ludo'', email: ''[email protected]'' }))); }); });

Más ejemplos (usando Jest, Mocha y AVA) en GitHub .


La respuesta de Mark es correcta. Middleware ejecuta esas instrucciones. Pero esto hace su vida más fácil: en la prueba, puede proporcionar lo que quiera como argumento a next() , y la función del generador lo recibirá como resultado del yield . Esto es exactamente lo que hace el middleware saga (excepto que en realidad enciende una solicitud en lugar de darle una respuesta falsa).

Para hacer que el yield obtenga un valor arbitrario, pásalo a next() . Para hacer que “reciba” un error, pásalo para throw() . En tu ejemplo:

it(''should return a LOGIN_FAIL action'', () => { const action = { payload: { name: ''toto'', password: ''123456'' } }; const generator = login(action); // Check that Saga asks to call the API expect( generator.next().value ).to.be.eql( call(api.login, action) ); // Note that *no actual request was made*! // We are just checking that the sequence of effects matches our expectations. // Check that Saga reacts correctly to the failure expect( generator.throw({ error: ''user not found'' }).value ).to.be.eql( put({ type: ''LOGIN_FAIL'', payload: { error: ''user not found'' } }) ); });