Metamorfosis dari pengujian redux-saga

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:

  1. Pengguna menekan tombol.
  2. Permintaan dikirim ke server, menginformasikan bahwa pengguna telah menekan tombol.
  3. Server mengembalikan jumlah klik yang dilakukan.
  4. Negara mencatat jumlah klik yang dilakukan.
  5. UI diperbarui, dan pengguna melihat bahwa jumlah klik telah meningkat.
  6. ...
  7. 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 .

Source: https://habr.com/ru/post/id458500/


All Articles