2016-02-26 11 views
22

Próbuję przetestować każdy scenariusz, który może spełnić moja saga, ale nie jestem w stanie sprawić, że zachowam się tak, jak chcę. Jest to bardzo proste, mam żądanie HTTP (logowanie) i chcę przetestować sukces i przypadki awarii, kpiąc z mojej metody API.Jak przetestować awarie żądania interfejsu API w Redux Saga?

Ale wygląda na to, że call effect nie uruchamia mojej funkcji api, nie wiem jeszcze jak to działa, ale domyślam się, że oprogramowanie pośredniczące jest odpowiedzialne za wywołanie funkcji, a ponieważ ja nie przejdź przez sklep na mój test, nie mogę uzyskać wyniku.

Moje pytanie brzmi: jak możesz przetestować swoją sagę, gdy musisz wysłać różne akcje (zazwyczaj sukces lub porażkę) obok połączenia asynchronicznego?

Spojrzałem na przykład, znalazłem sagi z sukcesu i fail ale fail sprawę nigdy nie jest testowany na przykład w przykładzie koszyka 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); 
} 

TEST.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 
    }); 
    }); 
}); 

Odpowiedz

36

Mark’s answer jest poprawne. Oprogramowanie pośrednie wykonuje te instrukcje. Ale to ułatwia życie: w teście możesz podać , niezależnie od tego, jak chcesz jako argument do next(), a funkcja generująca otrzyma je w wyniku yield. Jest to dokładnie to, co robi oprogramowanie pośrednie sagi (z wyjątkiem tego, że faktycznie uruchamia żądanie, zamiast dać fałszywą odpowiedź).

Aby uzyskać yield uzyskać dowolną wartość, należy przekazać ją do next(). Aby błąd "otrzymał", należy przekazać go do throw(). W przykładzie:

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' } 
    }) 
); 
}); 
7

Poprawnie - jak rozumiem, cały sens Redux-Saga jest to, że funkcja wykorzystuje API saga saga powrotu obiekty opisujące th e działanie, a następnie oprogramowanie pośrednie później sprawdza te obiekty, aby faktycznie wykonać to zachowanie. Tak więc, stwierdzenie yield call(myApiFunction, "/someEndpoint", arg1, arg2) w sadze może zwrócić obiekt, który teoretycznie wygląda na {effectType : CALL, function: myApiFunction, params: [arg1, arg2]}.

Możesz albo sprawdzić źródło redux-sagi, aby zobaczyć dokładnie, jak faktycznie wyglądają te deklaratywne obiekty i utworzyć pasujący obiekt do porównania w teście, albo użyć funkcji API, aby utworzyć obiekty (co jest moim zdaniem co redux-saga robi w swoim kodzie testowym).

0

też może chcieć użyć biblioteki pomocnika do sprawdzenia swoich sag, jak redux-saga-testing.

Zastrzeżenie: Napisałem tę bibliotekę do rozwiązania, że ​​identyczny problem

Biblioteka ta sprawi, że testowy wygląda jak każdy inny test (synchroniczne), który jest o wiele łatwiejsze do rozumu o ponad nazywając generator.next() ręcznie.

Biorąc przykład, można napisać testy następujące:

(nie jest to przy użyciu składni żartem, ale to w zasadzie to samo z Mocha, to całkowicie badanie biblioteka-agnostyk)

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]' }))); 
    }); 
}); 

Więcej przykładów (przy użyciu Jest, Mocha i AVA) pod numerem GitHub.