最新。頭版

  • 本文永久連結: https://dwatow.github.io/2020/04-20-jest/jest-doc-3/
  • 讀 Jest Doc - 非同步測試

    1. 讀 Jest Doc - 非同步測試
    2. Testing Asynchronous Code
      1. Callbacks
      2. Promises
        1. .resolves / .rejects
      3. Async/Await
      4. 下回見
    3. 問題

    讀 Jest Doc - 非同步測試

    上一回,我們看了各式各樣的基本的斷言庫,可以讓我們測試簡單型別(Number/String/Boolean),測試非零值(undefined/null),測試複雜型別(Object/Array),甚至還可以測試是否例外的發生行為,以及測試例外錯誤(Error)

    這一回,來看看 JavaScript 的宿命對決,要怎麼測試非同步問題。
    這個就沒什麼測試觀念要帶入 ,就純粹的在看是不是會寫 JavaScript 而已
    對非同步沒有經驗或不懂的朋友,可以先看看影片: 所以說event loop到底是什麼玩意兒?| Philip Roberts | JSConf EU 了解一下再繼續看哦

    Testing Asynchronous Code

    依照非同步的聖經的步調,來看非同步的三位一體吧!

    Callbacks

    待測物

    待測 function 是一個 fetchData 吃一個 callback 當參數。
    內部實作,就讓它隔一段時間再執行 callback,模仿 API 發送成功之後,呼叫 callback 的行為。

    function fetchData(callback) {
    setTimeout(() => {
    callback('peanut butter')
    }, 500);
    }

    測試程式

    測試千萬不要這樣寫

    // Don't do this!
    test('the data is peanut butter', () => {
    function callback(data) {
    expect(data).toBe('peanut butter');
    }

    fetchData(callback); // 執行 (最後一行) 就...結束了。
    });

    這樣寫的話, fetchData 因為在最後一行,所以只要它跑完就結束了。JavaScript 是非同步語言,所以這種發送 API 的事都是主執行緒結束時在處理的,但是主執行緒結束,Jest 也就結束了,沒有跑 callback 等同於沒有跑測試 QQ

    怎辦? Jest 給了我們一個 done 的用法 (在此為了不要和上一個例子差太多,所以我改寫了 jest 官網上的範例)

    test('the data is peanut butter', done => {
    function callback(data) {
    expect(data).toBe('peanut butter');
    done();
    }

    fetchData(callback);
    });

    只要你有引入 done 就一定要執行 done 不然就會失敗。
    算是很好的保障機制。 done() 的時間點會安排在測試的最後。
    如果有 try-catch 我建議放在 finally 的區段。

    Promises

    待測物

    待測 function 是一個 fetchData 吃一個 callback 當參數。
    內部實作,除了承上的特性之外,還需要讓它回傳一個 promise 物件。

    • 在成功時,在 setTimeout 裡執行 promise 的 resolve
    • 在失敗時,在 setTimeout 裡執行 promise 的 reject
    • 在例外發生時,就特別不一樣了,不能在 setTimeout 裡。
      因為設計 setTimeout 是為了在 API 成功回傳之後,執行的內容
      而 exception 發生的時間點,通常是在 new Promise() 的 callback 這一層。所以特別寫出這個待測物的細節。
    function fetchData(callback) {
    return new Promise(resolve => {
    setTimeout(() => {
    resolve('peanut butter')
    }, 500);
    })
    }

    function fetchDataReject(callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    reject('error')
    }, 500);
    })
    }
    function fetchDataErr(callback) {
    return new Promise((resolve, reject) => {
    throw new Error('error')
    setTimeout(() => {
    reject('error')
    }, 500);
    })
    }

    測試程式

    想要弄懂 Promise 可以先看看這一篇: JavaScript Promise:簡介  |  Web  |  Google Developers

    如果你使用 Promise 而 Promise 回傳 reject 的話,就會導致正式流程的測試失敗。

    注意: 使用 promise 可以不用 done,但是要記得在該 return 時加上 return。
    expect 寫在 .then.catch 裡的 callback 時, fetchData 前要加 return

    test('the data is peanut butter', () => {
    return fetchData().then(data => {
    expect(data).toBe('peanut butter');
    });
    });

    可靠的 Promise.reject()

    如果你想測試 Promise.reject() 可靠的執行。除了使用 .catch 註冊一個會執行 expect 的 callback 之外。最好再加一個 expect.assertions(1); 讓 Jest 知道你應該要執行一次 assertion。
    確保它真的會跑到 assertion 而不是就這樣默默的都執行對了。不會 reject 然後又沒報錯。

    test('the fetch fails with an error', () => {
    expect.assertions(1); // 讓 Jest 知道你應該要執行一次 assertion。
    return fetchData().catch(e => expect(e).toMatch('error'));
    });

    .resolves / .rejects

    Jest 斷言庫提供了 promise 的簡便寫法,這時就是 Effective Jest 的時刻了。

    注意: 使用 promise 可以不用 done ,但是要使用 .resolves / .rejects
    使用 .rejects 時,可以不用 expect.assertions(1);
    無論使用哪一種,最後要記得,在該 return 時加上 return。

    可靠的 Promise.resolve()

    test('the data is peanut butter(shorthand)', () => {
    return expect(fetchData()).resolves.toBe('peanut butter');
    });

    可靠的 Promise.reject()

    test('the fetch fails with an error(shorthand)', () => {
    return expect(fetchDataReject()).rejects.toMatch('error');
    });

    Async/Await

    使用 async/await 第一件事,就是要先會用 promise

    1. 記得在要執行 await 的函數開頭,加上 async。
    2. 用 try-catch 捕捉 reject 的結果。 (取代 .catch 的語法)
    test('the data is peanut butter', async () => {
    const data = await fetchData();
    expect(data).toBe('peanut butter');
    });

    test('the fetch fails with an error', async () => {
    expect.assertions(1);
    try {
    await fetchDataErr();
    } catch (e) {
    expect(e).toMatch('error');
    }
    });

    Jest 斷言庫提供了 async/await 的簡便寫法,這時就是 Effective Jest 的時刻了。

    在 .resolves / .rejects 的同法,也可以搭配 async/await

    test('the data is peanut butter(shorthand)', async () => {
    await expect(fetchData()).resolves.toBe('peanut butter');
    });

    test('the fetch fails with an reject error(shorthand)', async () => {
    await expect(fetchDataReject()).rejects.toMatch('error');
    });

    test('the fetch fails with an exception error(shorthand)', async () => {
    await expect(fetchDataErr()).rejects.toThrow('error');
    });

    下回見

    喜歡的話歡迎訂閱、按讚、分享。
    有任何問題也歡迎在下方留言討論。

    如果想參加聚會的話,可以私訊給我哦~
    我們下一篇見

    問題

    最後一個例子,不知道為什麼照官網寫的不能成功。
    => 感謝 @高培修 的解答,原來是我沒有把 reject 和 exception 的 case 弄清楚。

  • tags:
  • { jest }
  • { async }
  • { promise }
  • { callback }

標籤雲

.new framework 2018鐵人賽 API Doc Aglio Arduino Atom Block Boost Box Model C sharp CRUD CSS CSS Unit CSS3 C_and_Cpp Code Complete 2 Cpp沉思錄 Design Pattern Django ECMA-262 ECMAScript Flex Git HPX HTML Inline JavaScript Jenkins KnR2 LIFF LINE MFC controls MOPCON Media Query OAuth Position Pseudo-elements RWD Raspberry Pi 4 Render tree Rulest Order SVN Selector Sliverlight Specificity TC39 TDD TED TED特區 Transition Ubuntu Vendor Prefix WIN32 API/MFC XML XPath XSLT Xilinx angular1 angularjs animation api assertion async awk axios babel background bash blogger body bootstrap border bugTracker callback canvas chatbot cloud fonts component controller cpp css css-loader database design pattern display docker docsify e2e env eslint exporess express facebook file api file-loader filter flex-grow flex-shrink fontawesome foreign key git git reset git-ftp gitalk google gulp hackmd hash heroku hexo http https husky import iview ivuew javascript jest jquery json keyframes linux login mariadb markdown mentor migration mixin mock model monent nginx ngork node-sass nodejs npm oo opacity orm outline pm2 postman prettier promise protractor proxy pure component python sequelize shell sign-in spy ssl stub sublime text2 swap swing dance test double transition unit test unit testing v-model vee-validate viewport visibility vue vue-cli vue-loader vue-masonry vue-router vue-slot vuex web camp webpack webpack loader which wiki zip zsh 七股宛蓁家兩天一夜 三相整流 三相發電 中柱 人月神話 人機互動 你所不知道的JS 你所不知道的js 再讀一本書 凱宇皓月 動畫特效 十年之後成為大師 台南 和道服說話 圖解柔道之術 團隊溝通技巧 在道館的日子 好想工作室 字型設定 完美 camp 進化論 家家有本難唸的經 小旅行 工業風 影像處理 後搖臂 怦然心動的人生整理魔法 手把開關 抱怨VC6 招募 日舞台南 服三的日子 東岸軍旅 柔道社與我 流浪文章 測試工具 爛 code 收集器 爬蟲 版本控制 狼記事 生平處女秀之電腦裝機 生活小事 直流 CDI 看書 研究所的日子 社大的日子 社服社與我 系統設計 網誌記事 網頁前端技術 舊秋調 藝術 行列輸入法 設計 設計師的路 資料庫 資料庫正規則 踩發桿 追逐我在墾丁*天氣晴 避震器 重構 野狼 鈞祐的獨立山兩天一夜露營 電子DIY 電路 音響拆裝過程 高壓線圈 coil