<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      全面掌握 Jest:從零開(kāi)始的測(cè)試指南(下篇)

      在上一篇測(cè)試指南中,我們介紹了Jest 的背景、如何初始化項(xiàng)目、常用的匹配器語(yǔ)法以及鉤子函數(shù)的使用。這一篇篇將繼續(xù)深入探討 Jest 的高級(jí)特性,包括 Mock 函數(shù)、異步請(qǐng)求的處理、Mock 請(qǐng)求的模擬、類(lèi)的模擬以及定時(shí)器的模擬、snapshot 的使用。通過(guò)這些技術(shù),我們將能夠更高效地編寫(xiě)和維護(hù)測(cè)試用例,尤其是在處理復(fù)雜異步邏輯和外部依賴(lài)時(shí)。

      Mock 函數(shù)

      假設(shè)存在一個(gè) runCallBack 函數(shù),其作用是判斷入?yún)⑹欠駷楹瘮?shù),如果是,則執(zhí)行傳入的函數(shù)。

      export const runCallBack = (callback) => {
        typeof callback == "function" && callback();
      };
      

      編寫(xiě)測(cè)試用例

      我們先嘗試編寫(xiě)它的測(cè)試用例:

      import { runCallBack } from './func';
      test("測(cè)試 runCallBack", () => {
        const fn = () => {
          return "hello";
        };
        expect(runCallBack(fn)).toBe("hello");
      });
      

      此時(shí),命令行會(huì)報(bào)錯(cuò)提示 runCallBack(fn) 執(zhí)行的返回值為 undefined,而不是 "hello"。如果期望得到正確的返回值,就需要修改原始的 runCallBack 函數(shù),但這種做法不符合我們的測(cè)試預(yù)期——我們不希望為了測(cè)試而改變?cè)械臉I(yè)務(wù)功能。

      這時(shí),mock 函數(shù)就可以很好地解決這個(gè)問(wèn)題。mock 可以用來(lái)模擬一個(gè)函數(shù),并可以自定義函數(shù)的返回值。我們可以通過(guò) mock 函數(shù)來(lái)分析其調(diào)用次數(shù)、入?yún)⒑统鰠⒌刃畔ⅰ?/p>

      使用 mock 解決問(wèn)題

      上述測(cè)試用例可以改為如下形式:

      test("測(cè)試 runCallBack", () => {
        const fn = jest.fn();
        runCallBack(fn);
        expect(fn).toBeCalled();
        expect(fn.mock.calls.length).toBe(1);
      });
      

      這里,toBeCalled() 用于檢查函數(shù)是否被調(diào)用過(guò),fn.mock.calls.length 用于檢查函數(shù)被調(diào)用的次數(shù)。

      mock 屬性中還有一些有用的參數(shù):

      • calls: 數(shù)組,保存著每次調(diào)用時(shí)的入?yún)ⅰ?/li>
      • instances: 數(shù)組,保存著每次調(diào)用時(shí)的實(shí)例對(duì)象。
      • invocationCallOrder: 數(shù)組,保存著每次調(diào)用的順序。
      • results: 數(shù)組,保存著每次調(diào)用的執(zhí)行結(jié)果。

      自定義返回值

      mock 還可以自定義返回值。可以在 jest.fn 中定義回調(diào)函數(shù),或者通過(guò) mockReturnValuemockReturnValueOnce 方法定義返回值。

      test("測(cè)試 runCallBack 返回值", () => {
        const fn = jest.fn(() => {
          return "hello";
        });
        createObject(fn);
        expect(fn.mock.results[0].value).toBe("hello");
       
        fn.mockReturnValue('alice') // 定義返回值
        createObject(fn);
        expect(fn.mock.results[1].value).toBe("alice");
        fn.mockReturnValueOnce('x') // 定義只返回一次的返回值
        createObject(fn);
        expect(fn.mock.results[2].value).toBe("x");
        createObject(fn);
        expect(fn.mock.results[3].value).toBe("alice");
      });
      

      構(gòu)造函數(shù)的模擬

      構(gòu)造函數(shù)作為一種特殊的函數(shù),也可以通過(guò) mock 實(shí)現(xiàn)模擬。

      // func.js
      export const createObject = (constructFn) => {
        typeof constructFn == "function" && new constructFn();
      };
      
      // func.test.js
      import { createObject } from './func';
      test("測(cè)試 createObject", () => {
          const fn = jest.fn();
          createObject(fn);
          expect(fn).toBeCalled();
          expect(fn.mock.calls.length).toBe(1);
      });
      

      通過(guò)使用 mock 函數(shù),我們可以更好地模擬函數(shù)的行為,并分析其調(diào)用情況。這樣不僅可以避免修改原有業(yè)務(wù)邏輯,還能確保測(cè)試的準(zhǔn)確性和可靠性。

      異步代碼

      在處理異步請(qǐng)求時(shí),我們期望 Jest 能夠等待異步請(qǐng)求結(jié)束后再對(duì)結(jié)果進(jìn)行校驗(yàn)。測(cè)試請(qǐng)求接口地址使用 http://httpbin.org/get,可以將參數(shù)通過(guò) query 的形式拼接在 URL 上,如 http://httpbin.org/get?name=alice。這樣接口返回的數(shù)據(jù)中將攜帶 { name: 'alice' },可以依此來(lái)對(duì)代碼進(jìn)行校驗(yàn)。

      以下分別通過(guò)異步請(qǐng)求回調(diào)函數(shù)、Promise 鏈?zhǔn)秸{(diào)用、await 的方式獲取響應(yīng)結(jié)果來(lái)進(jìn)行分析。

      回調(diào)函數(shù)類(lèi)型

      回調(diào)函數(shù)的形式通過(guò) done() 函數(shù)告訴 Jest 異步測(cè)試已經(jīng)完成。

      func.js 文件中通過(guò) Axios 發(fā)送 GET 請(qǐng)求:

      const axios = require("axios");
      
      export const getDataCallback = (url, callbackFn) => {
        axios.get(url).then(
          (res) => {
            callbackFn && callbackFn(res.data);
          },
          (error) => {
            callbackFn && callbackFn(error);
          }
        );
      };
      

      func.test.js 文件中引入發(fā)送請(qǐng)求的方法:

      import { getDataCallback } from "./func";
      test("回調(diào)函數(shù)類(lèi)型-成功", (done) => {
        getDataCallback("http://httpbin.org/get?name=alice", (data) => {
          expect(data.args).toEqual({ name: "alice" });
          done();
        });
      });
      
      test("回調(diào)函數(shù)類(lèi)型-失敗", (done) => {
        getDataCallback("http://httpbin.org/xxxx", (data) => {
          expect(data.message).toContain("404");
          done();
        });
      });
      

      promise類(lèi)型

      Promise 類(lèi)型的用例中,需要使用 return 關(guān)鍵字來(lái)告訴 Jest 測(cè)試用例的結(jié)束時(shí)間。

      // func.js
      export const getDataPromise = (url) => {
        return axios.get(url);
      };
      

      Promise 類(lèi)型的函數(shù)可以通過(guò) then 函數(shù)來(lái)處理:

      // func.test.js
      test("Promise 類(lèi)型-成功", () => {
        return getDataPromise("http://httpbin.org/get?name=alice").then((res) => {
          expect(res.data.args).toEqual({ name: "alice" });
        });
      });
      
      test("Promise 類(lèi)型-失敗", () => {
        return getDataPromise("http://httpbin.org/xxxx").catch((res) => {
          expect(res.response.status).toBe(404);
        });
      });
      

      也可以直接通過(guò) resolvesrejects 獲取響應(yīng)的所有參數(shù)并進(jìn)行匹配:

      test("Promise 類(lèi)型-成功匹配對(duì)象t", () => {
        return expect(
          getDataPromise("http://httpbin.org/get?name=alice")
        ).resolves.toMatchObject({
          status: 200,
        });
      });
      
      test("Promise 類(lèi)型-失敗拋出異常", () => {
        return expect(getDataPromise("http://httpbin.org/xxxx")).rejects.toThrow();
      });
      

      await 類(lèi)型

      上述 getDataPromise 也可以通過(guò) await 的形式來(lái)編寫(xiě)測(cè)試用例:

      test("await 類(lèi)型-成功", async () => {
        const res = await getDataPromise("http://httpbin.org/get?name=alice");
        expect(res.data.args).toEqual({ name: "alice" });
      });
      
      test("await 類(lèi)型-失敗", async () => {
        try {
          await getDataPromise("http://httpbin.org/xxxx")
        } catch(e){
          expect(e.status).toBe(404)
        }
      });
      

      通過(guò)上述幾種方式,可以有效地編寫(xiě)異步函數(shù)的測(cè)試用例。回調(diào)函數(shù)Promise 鏈?zhǔn)秸{(diào)用以及 await 的方式各有優(yōu)劣,可以根據(jù)具體情況選擇合適的方法。

      Mock 請(qǐng)求/類(lèi)/Timers

      在前面處理異步代碼時(shí),是根據(jù)真實(shí)的接口內(nèi)容來(lái)進(jìn)行校驗(yàn)的。然而,這種方式并不總是最佳選擇。一方面,每個(gè)校驗(yàn)都需要發(fā)送網(wǎng)絡(luò)請(qǐng)求獲取真實(shí)數(shù)據(jù),這會(huì)導(dǎo)致測(cè)試用例執(zhí)行時(shí)間較長(zhǎng);另一方面,接口格式是否滿(mǎn)足要求是后端開(kāi)發(fā)者需要著重測(cè)試的內(nèi)容,前端測(cè)試用例并不需要涵蓋這部分內(nèi)容。

      在之前的函數(shù)測(cè)試中,我們使用了 Mock 來(lái)模擬函數(shù)。實(shí)際上,Mock 不僅可以用來(lái)模擬函數(shù),還可以模擬網(wǎng)絡(luò)請(qǐng)求和文件。

      Mock 網(wǎng)絡(luò)請(qǐng)求

      Mock 網(wǎng)絡(luò)請(qǐng)求有兩種方式:一種是直接模擬發(fā)送請(qǐng)求的工具(如 Axios),另一種是模擬引入的文件。

      直接模擬 Axios

      首先,在 request.js 中定義發(fā)送網(wǎng)絡(luò)請(qǐng)求的邏輯:

      import axios from "axios";
      
      export const fetchData = () => {
        return axios.get("/").then((res) => res.data);
      };
      

      然后,使用 jest 模擬 axios 即 jest.mock("axios"),并通過(guò) axios.get.mockResolvedValue 來(lái)定義響應(yīng)成功的返回值:

      const axios = require("axios");
      import { fetchData } from "./request";
      
      jest.mock("axios");
      test("測(cè)試 fetchData", () => {
        axios.get.mockResolvedValue({
          data: "hello",
        });
        return fetchData().then((data) => {
          expect(data).toEqual("hello");
        });
      });
      
      模擬引入的文件

      如果希望模擬 request.js 文件,可以在當(dāng)前目錄下創(chuàng)建 __mocks__ 文件夾,并在其中創(chuàng)建同名的 request.js 文件來(lái)定義模擬請(qǐng)求的內(nèi)容:

      // __mocks__/request.js
      export const fetchData = () => {
        return new Promise((resolve, reject) => {
          resolve("world");
        });
      };
      

      使用 jest.mock('./request') 語(yǔ)法,Jest 在執(zhí)行測(cè)試用例時(shí)會(huì)自動(dòng)將真實(shí)的請(qǐng)求文件內(nèi)容替換成 __mocks__/request.js 的文件內(nèi)容:

      // request.test.js
      import { fetchData } from "./request";
      jest.mock("./request");
      
      test("測(cè)試 fetchData", () => {
        return fetchData().then((data) => {
          expect(data).toEqual("world");
        });
      });
      

      如果部分內(nèi)容需要從真實(shí)的文件中獲取,可以通過(guò) jest.requireActual() 函數(shù)來(lái)實(shí)現(xiàn)。取消模擬則可以使用 jest.unmock()

      Mock 類(lèi)

      假設(shè)在業(yè)務(wù)場(chǎng)景中定義了一個(gè)工具類(lèi),類(lèi)中有多個(gè)方法,我們需要對(duì)類(lèi)中的方法進(jìn)行測(cè)試。

      // util.js
      export default class Util {
        add(a, b) {
          return a + b;
        }
        create() {}
      }
      
      // util.test.js
      import Util from "./util";
      test("測(cè)試add方法", () => {
        const util = new Util();
        expect(util.add(2, 5)).toEqual(7);
      });
      

      此時(shí),另一個(gè)文件如 useUtil.js 也用到了 Util 類(lèi):

      // useUtil.js
      import Util from "./util";
      
      export function useUtil() {
        const util = new Util();
        util.add(2, 6);
        util.create();
      }
      

      在編寫(xiě) useUtil 的測(cè)試用例時(shí),我們只希望測(cè)試當(dāng)前文件,并不希望重新測(cè)試 Util 類(lèi)的功能。這時(shí)也可以通過(guò) Mock 來(lái)實(shí)現(xiàn)。

      __mock__ 文件夾下創(chuàng)建模擬文件

      可以在 __mock__ 文件夾下創(chuàng)建 util.js 文件,文件中定義模擬函數(shù):

      // __mock__/util.js
      const Util = jest.fn()
      Util.prototype.add = jest.fn()
      Util.prototype.create = jest.fn();
      export default Util;
      
      // useUtil.test.js
      jest.mock("./util");
      import Util from "./util";
      import { useUtilFunc } from "./useUtil";
      
      test("useUtil", () => {
        useUtilFunc();
        expect(Util).toHaveBeenCalled();
        expect(Util.mock.instances[0].add).toHaveBeenCalled();
        expect(Util.mock.instances[0].create).toHaveBeenCalled();
      });
      
      在當(dāng)前 .test.js 文件定義模擬函數(shù)

      也可以在當(dāng)前 .test.js 文件中定義模擬函數(shù):

      // useUtil.test.js
      import { useUtilFunc } from "./useUtil";
      import Util from "./util";
      jest.mock("./util", () => {
        const Util = jest.fn();
        Util.prototype.add = jest.fn();
        Util.prototype.create = jest.fn();
        return Util
      });
      test("useUtil", () => {
        useUtilFunc();
        expect(Util).toHaveBeenCalled();
        expect(Util.mock.instances[0].add).toHaveBeenCalled();
        expect(Util.mock.instances[0].create).toHaveBeenCalled();
      });
      

      這兩種方式都可以模擬類(lèi)。

      Timers

      在定義一些功能函數(shù)時(shí),比如防抖和節(jié)流,經(jīng)常會(huì)使用 setTimeout 來(lái)推遲函數(shù)的執(zhí)行。這類(lèi)功能也可以通過(guò) Mock 來(lái)模擬測(cè)試。

      // timer.js
      export const timer = (callback) => {
        setTimeout(() => {
          callback();
        }, 3000);
      };
      
      使用 done 異步執(zhí)行

      一種方式是使用 done 來(lái)異步執(zhí)行:

      import { timer } from './timer'
      
      test("timer", (done) => {
        timer(() => {
          done();
          expect(1).toBe(1);
        });
      });
      
      使用 Jest 的 timers 方法

      另一種方式是使用 Jest 提供的 timers 方法,通過(guò) useFakeTimers 啟用假定時(shí)器模式,runAllTimers 來(lái)手動(dòng)運(yùn)行所有的定時(shí)器,并使用 toHaveBeenCalledTimes 來(lái)檢查調(diào)用次數(shù):

      beforeEach(()=>{
          jest.useFakeTimers()
      })
      
      test('timer測(cè)試', ()=>{
          const fn = jest.fn();
          timer(fn);
          jest.runAllTimers();
          expect(fn).toHaveBeenCalledTimes(1);
      })
      

      此外,還有 runOnlyPendingTimers 方法用來(lái)執(zhí)行當(dāng)前位于隊(duì)列中的 timers,以及 advanceTimersByTime 方法用來(lái)快進(jìn) X 毫秒。

      例如,在存在嵌套的定時(shí)器時(shí),可以通過(guò) advanceTimersByTime 快進(jìn)來(lái)模擬:

      // timer.js
      export const timerTwice = (callback) => {
        setTimeout(() => {
          callback();
          setTimeout(() => {
            callback();
          }, 3000);
        }, 3000);
      };
      
      // timer.test.js
      import { timerTwice } from "./timer";
      test("timerTwice 測(cè)試", () => {
        const fn = jest.fn();
        timerTwice(fn);
        jest.advanceTimersByTime(3000);
        expect(fn).toHaveBeenCalledTimes(1);
        jest.advanceTimersByTime(3000);
        expect(fn).toHaveBeenCalledTimes(2);
      });
      

      無(wú)論是模擬網(wǎng)絡(luò)請(qǐng)求、類(lèi)還是定時(shí)器,Mock 都是一個(gè)強(qiáng)大的工具,可以幫助我們構(gòu)建可靠且高效的測(cè)試用例。

      snapshot

      假設(shè)當(dāng)前存在一個(gè)配置,配置的內(nèi)容可能會(huì)經(jīng)常變更,如下所示:

      export const generateConfig = () => {
        return {
          server: "http://localhost",
          port: 8001,
          domain: "localhost",
        };
      };
      
      toEqual 匹配

      如果對(duì)它進(jìn)行測(cè)試用例編寫(xiě),最簡(jiǎn)單的方式就是使用 toEqual 匹配,如下所示:

      import { generateConfig } from "./snapshot";
      
      test("測(cè)試 generateConfig", () => {
        expect(generateConfig()).toEqual({
          server: "http://localhost",
          port: 8001,
          domain: "localhost",
        });
      });
      

      但是這種方式存在一些問(wèn)題:每當(dāng)配置文件發(fā)生變更時(shí),都需要修改測(cè)試用例。為了避免測(cè)試用例頻繁修改,可以通過(guò) snapshot 快照來(lái)解決這個(gè)問(wèn)題。

      toMatchSnapshot

      通過(guò) toMatchSnapshot 函數(shù)生成快照:

      test("測(cè)試 generateConfig", () => {
        expect(generateConfig()).toMatchSnapshot();
      });
      

      第一次執(zhí)行 toMatchSnapshot 時(shí),會(huì)生成一個(gè) __snapshots__ 文件夾,里面存放著 xxx.test.js.snap 這樣的文件,內(nèi)容是當(dāng)前配置的執(zhí)行結(jié)果。

      第二次執(zhí)行時(shí),會(huì)生成一個(gè)新的快照并與已有的快照進(jìn)行比較。如果相同則測(cè)試通過(guò);如果不相同,測(cè)試用例不通過(guò),并且在命令行會(huì)提示你是否需要更新快照,如 “1 snapshot failed from 1 test suite. Inspect your code changes or press u to update them”。

      按下 u 鍵之后,測(cè)試用例會(huì)通過(guò),并且覆蓋原有的快照。

      快照的值不同

      如果該函數(shù)每次的值不同,生成的快照也不相同,例如每次調(diào)用函數(shù)返回時(shí)間戳:

      export const generateConfig = () => {
        return {
          server: "http://localhost",
          port: 8002,
          domain: "localhost",
          date: new Date()
        };
      };
      

      在這種情況下,toMatchSnapshot 可以接受一個(gè)對(duì)象作為參數(shù),該對(duì)象用于描述快照中的某些字段應(yīng)該如何匹配:

      test("測(cè)試 generateConfig", () => {
        expect(generateConfig()).toMatchSnapshot({
          date: expect.any(Date)
        });
      });
      
      行內(nèi)快照

      上述的快照是在 __snapshots__ 文件夾下生成的,還有一種方式是通過(guò) toMatchInlineSnapshot 在當(dāng)前的 .test.js 文件中生成。需要注意的是,這種方式通常需要配合 prettier 工具來(lái)使用。

      test("測(cè)試 generateConfig", () => {
        expect(generateConfig()).toMatchInlineSnapshot({
          date: expect.any(Date),
        });
      });
      

      測(cè)試用例通過(guò)后,該用例的格式如下:

      test("測(cè)試 generateConfig", () => {
        expect(generateConfig()).toMatchInlineSnapshot({
        date: expect.any(Date)
      }, `
      {
        "date": Any<Date>,
        "domain": "localhost",
        "port": 8002,
        "server": "http://localhost",
      }
      `);
      });
      

      使用 snapshot 測(cè)試可以有效地減少頻繁修改測(cè)試用例的工作量。無(wú)論配置如何變化,只需要更新一次快照即可保持測(cè)試的一致性。

      本篇及上一篇文章的內(nèi)容合在一起涵蓋了 Jest 的基本使用和高級(jí)配置。更多有關(guān)前端工程化的內(nèi)容,請(qǐng)參考我的其他博文,持續(xù)更新中~

      posted @ 2024-09-18 20:11  一顆冰淇淋  閱讀(335)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 性做久久久久久久久| 国产一区二区亚洲一区二区三区| 一本精品99久久精品77| 国产高潮又爽又刺激的视频| 麻豆国产成人AV在线播放| 国产日韩精品视频无码| 黄页网站在线观看免费视频| 色午夜一av男人的天堂| 少妇熟女视频一区二区三区| 给我播放片在线观看| 精品人妻少妇一区二区三区在线| 九九电影网午夜理论片| 日韩深夜视频在线观看| 2020国产成人精品视频| 久久国内精品自在自线91| 亚洲欧洲色图片网站| 国产精品综合一区二区三区| 久99久热免费视频播放| 国产又色又爽又黄的视频在线| 亚洲鸥美日韩精品久久| 中日韩中文字幕一区二区| 欧美性猛交xxxx免费看| 亚洲精品入口一区二区乱| 国产精品视频中文字幕| 欧美牲交40_50a欧美牲交aⅴ| 亚洲成av人片无码不卡播放器| 亚洲一区二区视频在线观看| 亚洲国产天堂久久综合网| 视频二区国产精品职场同事| 国产又色又爽又黄的网站免费| 亚洲综合一区二区三区在线| 久久久久青草线综合超碰| 日本视频一两二两三区| 好吊妞视频这里有精品| 2022最新国产在线不卡a| 亚洲精品一区二区三区蜜臀| 亚洲国产中文字幕在线视频综合| 豆国产97在线 | 亚洲| 不卡乱辈伦在线看中文字幕| 国产97色在线 | 免费| 中文日产幕无线码一区中文|