Kerangka kerja
redux-saga
menyediakan banyak pola menarik untuk bekerja dengan efek samping, tetapi, seperti pengembang perusahaan yang benar-benar berdarah, kita perlu menutupi seluruh kode kita dengan pengujian. Mari kita cari tahu bagaimana kita akan menguji kisah-kisah kita.

Ambil clicker paling sederhana sebagai contoh. Alur data dan makna aplikasi adalah sebagai berikut:
- Pengguna menekan tombol.
- Permintaan dikirim ke server, menginformasikan bahwa pengguna telah menekan tombol.
- Server mengembalikan jumlah klik yang dilakukan.
- Negara mencatat jumlah klik yang dilakukan.
- UI diperbarui, dan pengguna melihat bahwa jumlah klik telah meningkat.
- ...
- KEUNTUNGAN.
Dalam pekerjaan kami, kami menggunakan naskah, jadi semua contoh akan ada dalam bahasa ini.
Seperti yang mungkin sudah Anda tebak, kami akan menerapkan semua ini dengan
redux-saga
. Berikut adalah kode untuk seluruh file kisah:
export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) }
Dalam contoh sederhana ini, kami mendeklarasikan saga
processClick
, yang secara langsung memproses aksi dan saga
watchClick
, yang membuat loop untuk memproses
action'
.
Generator
Jadi kami memiliki kisah yang paling sederhana. Ia mengirimkan permintaan ke server
( call)
, menerima hasilnya dan meneruskannya ke peredam
( put)
. Kita perlu entah bagaimana menguji apakah kisah itu mentransmisikan persis apa yang diterimanya dari server. Mari kita mulai.
Untuk pengujian, kita perlu mengunci panggilan server dan entah bagaimana memeriksa apakah persis apa yang datang dari server masuk ke peredam.
Karena sagas adalah fungsi generator, metode
next()
, yang ada dalam prototipe generator, akan menjadi cara yang paling jelas untuk menguji. Saat menggunakan metode ini, kami memiliki kesempatan untuk menerima nilai berikutnya dari generator dan mentransfer nilai ke generator. Dengan demikian, kita keluar dari kotak kesempatan untuk mendapatkan panggilan basah. Tapi apakah semuanya begitu cerah? Berikut adalah tes yang saya tulis pada generator kosong:
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))) })
Tesnya singkat, tapi apa tesnya? Bahkan, itu hanya mengulangi kode metode saga, yaitu, dengan perubahan apa pun dalam saga, tes harus diubah.
Tes semacam itu tidak membantu dalam pengembangan.
Rencana uji redux-saga
Setelah menghadapi masalah ini, kami memutuskan untuk mencari di google dan tiba-tiba menyadari bahwa kami bukan satu-satunya dan jauh dari yang pertama. Langsung
dalam dokumentasi untuk
redux-saga
pengembang menawarkan beberapa perpustakaan yang dibuat khusus untuk memuaskan penggemar pengujian.
Dari daftar yang diusulkan kami mengambil perpustakaan
redux-saga-test-plan
. Berikut adalah kode untuk versi pertama dari tes yang saya tulis dengannya:
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() })
Konstruktor tes di
redux-saga-test-plan
adalah fungsi
expectSaga
, yang mengembalikan antarmuka yang menggambarkan tes. Test saga (
processClick
dari daftar pertama) diteruskan ke fungsi itu sendiri.
Menggunakan metode
provide
, Anda dapat memblokir panggilan server atau dependensi lainnya. Array
StaticProvider'
, yang menggambarkan metode mana yang harus dikembalikan.
Di blok
Act
, kami memiliki satu metode tunggal -
dispatch
. Suatu tindakan diberikan padanya, yang akan direspon oleh hikayat tersebut.
Blok
assert
terdiri dari metode
call put
, yang memeriksa apakah efek yang sesuai disebabkan selama pekerjaan saga.
Semuanya berakhir dengan metode
run()
. Metode ini menjalankan tes secara langsung.
Keuntungan dari pendekatan ini:
- Ia memeriksa apakah metode itu dipanggil, dan bukan urutan panggilan;
- moki dengan jelas menjelaskan fungsi mana yang basah dan apa yang kembali.
Namun, ada pekerjaan yang harus dilakukan:
- ada lebih banyak kode;
- tesnya sulit dibaca;
- ini adalah tes untuk perilaku, yang berarti masih terhubung dengan implementasi saga.
Dua pukulan terakhir
Tes kondisi
Pertama, kami memperbaiki yang terakhir: kami membuat tes keadaan dari tes perilaku. Fakta bahwa
test-plan
memungkinkan Anda untuk mengatur
state
awal dan lulus
reducer
, yang harus menanggapi efek
put
dihasilkan oleh saga, akan membantu kami dengan ini. Ini terlihat seperti ini:
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)) })
Dalam tes ini, kami tidak lagi memverifikasi bahwa ada efek yang dipicu. Kami memeriksa status akhir setelah eksekusi, dan itu tidak masalah.
Kami berhasil menyingkirkan implementasi saga, sekarang mari kita coba membuat tes lebih dimengerti. Ini mudah jika Anda mengganti
then()
dengan
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) })
Tes integrasi
Tetapi bagaimana jika kita juga mendapatkan operasi klik terbalik (sebut saja batalkan klik), dan sekarang file jebol kami terlihat seperti ini:
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()]) }
Misalkan kita perlu menguji bahwa ketika tindakan klik dan hapus klik disebut dalam status, hasil dari perjalanan terakhir ke server ditulis ke status. Tes semacam itu juga dapat dengan mudah dilakukan dengan
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) })
Harap dicatat bahwa sekarang kami sedang menguji
mainSaga
, dan bukan penangan
mainSaga
individu.
Namun, jika kita menjalankan tes ini apa adanya, kita akan mendapatkan Vorning:

Ini karena efek
takeEvery
- ini adalah loop pemrosesan pesan yang akan berfungsi saat aplikasi kita terbuka. Oleh karena itu, tes yang disebut
takeEvery
tidak akan dapat menyelesaikan pekerjaan tanpa bantuan dari luar, dan
redux-saga-test-plan
secara paksa mengakhiri efek ini setelah 250 ms setelah dimulainya tes. Waktu habis ini dapat diubah dengan memanggil expectSaga.DEFAULT_TIMEOUT = 50.
Jika Anda tidak ingin menerima penilaian seperti itu, satu tes untuk setiap tes dengan efek kompleks, cukup gunakan metode silentRun()
alih-alih metode run()
.
Perangkap
Di mana tanpa jebakan ... Pada saat penulisan ini, versi terbaru redux-saga: 1.0.2. Pada saat yang sama,
redux-saga-test-plan
hanya dapat bekerja dengannya di JS.
Jika Anda ingin TypeScript, Anda harus menginstal versi dari saluran beta:
npm install redux-saga-test-plan@beta
dan matikan tes dari build. Untuk melakukan ini, dalam file tsconfig.json, Anda perlu menentukan path "./src/**/*.spec.ts" di bidang "kecualikan".
Meskipun demikian, kami menganggap
redux-saga-test-plan
pustaka terbaik untuk pengujian
redux-saga
. Jika Anda memiliki
redux-saga
di proyek Anda, mungkin itu akan menjadi pilihan yang baik untuk Anda.
Kode sumber contoh di
GitHub .