2016-02-26 109 views
22

我試圖測試我的傳奇可以遵循的每個場景,但是我無法做出我想要的行爲。 這很簡單,我有一個HTTP請求(登錄),我想通過嘲笑我的API方法來測試成功和失敗的情況。如何使用Redux Saga測試API請求失敗?

但是,它看起來像call effect不會觸發我的API函數,我真的不知道它是如何工作的,但我想中間件負責調用該函數,並且因爲我沒有去我的測試商店,我無法得到結果。

所以我的問題是,當你需要在異步調用旁邊調度不同的動作(通常是成功或失敗)時,如何測試你的傳奇?

我找了一個例子,我發現成功的傳奇故事和失敗,但失敗的情況下,從來沒有測試,例如在購物車例子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 
    }); 
    }); 
}); 

回答

36

Mark’s answer是正確的。中間件執行這些指令。但是這會讓你的生活變得更輕鬆:在測試中,你可以提供作爲next()的參數,並且生成器函數將由yield接收它。這正是傳統中間件所做的(除了它實際上觸發了一個請求而不是給你一個假響應)。

要使yield獲得任意值,請將其傳遞給next()。爲了讓它「接收」一個錯誤,將它傳遞給throw()。在您的例子:

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

正確 - 我的理解是,終極版 - 佐賀的整點是,你的傳奇函數使用API​​的傳奇返回描述對象的第e行爲,然後中間件稍後查看這些對象以實際執行行爲。因此,一個傳奇中的yield call(myApiFunction, "/someEndpoint", arg1, arg2)聲明可能會返回一個概念上看起來像{effectType : CALL, function: myApiFunction, params: [arg1, arg2]}的對象。

您可以檢查redux-saga源,準確查看這些聲明對象的實際外觀,並創建一個匹配的對象以在您的測試中進行比較,或者使用API​​函數自己創建對象(這是我認爲的他們的測試代碼中會做什麼)。

0

您可能還需要使用一個輔助庫來測試你的傳奇故事,如redux-saga-testing

免責聲明:我寫了這個庫,以解決相同問題

這個庫將使您的測試看起來像任何其他(同步)測試,這是一個更容易推理比手動調用generator.next()

以你的榜樣,你可以寫測試如下:

(它使用玩笑語法,但它本質上與摩卡一樣,它是完全的測試庫無關)

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

更多的例子(使用Jest,Mocha和AVA)在GitHub上。