iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0

先講解第一種最常見的方法吧~
首先我們要模擬LocalStorage做一個假的LocalStorageMock:

LocalStorageMock主要有五個東西

  1. 存儲空間store,這邊我們用Record來寫他的型別,Record<K,T>可以紀錄所有物件裡keyvalue的型別,K是key的型別,T是value的型別
  2. getItem: 給予key取得store裡的value
  3. setItem: 將資料存取進store中,valueunknown但是最終會存進去string,所以我寫as known as string
  4. remove: 給予key取得store裡的value ,雖然我們這次不會用到,但是最好還是把所有實際上可能會用到的功能寫上去
  5. clear: 清除store
import { renderHook, act } from "@testing-library/react";
import { useLocalStorage } from "../src"; // 導入你的 useLocalStorage hook

// 模擬 LocalStorage
class LocalStorageMock {
  store: Record<string, string> = {};

  getItem(key: string) {
    return this.store[key] || null;
  }

  setItem<T>(key: string, value: T) {
    this.store[key] = value as unknown as string;
  }

  removeItem(key: string) {
    delete this.store[key];
  }

  clear() {
    this.store = {};
  }
}

接下來我們要把這個假的LocalStorageMock替代window裡的真的localStorage

describe("useLocalStorage", () => {
  let localStorageMock: LocalStorageMock;
  beforeEach(() => {
    localStorageMock.clear();
  });
  beforeAll(() => {
    localStorageMock = new LocalStorageMock();
    Object.defineProperty(window, "localStorage", {
      value: localStorageMock,
    });
  });

這邊我們用到兩個新的東西,beforeEachbeforeAll

  1. beforeAll :所在區域內會第一個執行。
  2. beforeEach :每一次的測試前會先執行。

順便補充另外兩個

  1. afterAll :所在區域內最後一個執行。
  2. afterEach :每一次的測試後會馬上執行。

區域是指如果放在describe 裡面,像block scope一樣,describe裡面就是區域

每一次測試是指ittest (雖然我們沒用到test,但他其實跟it差不多)

// 這行意思是將全域window的localStorage的內容改成localStorageMock
Object.defineProperty(window, "localStorage", {
      value: localStorageMock,
    });

接下來我們要寫測試了

  1. 測試沒有資料時我們初始值有被設定進localStorage並且取得到
  2. 能夠接受他是callback function
  3. 能夠接受是array
  4. 如果一開始LocalStorage就有存取資料則使用存取資料當預設值
  5. 可以設定新資料
  6. 可以用callback function 設定新資料
it("should retrieve the default value when local storage is empty", () => {
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    expect(result.current[0]).toBe("default");
  });

  it("Initial state is a callback function", () => {
    const { result } = renderHook(() => useLocalStorage("key", () => "value"));

    expect(result.current[0]).toBe("value");
  });

  it("Initial state is an array", () => {
    const { result } = renderHook(() => useLocalStorage("digits", [1, 2]));

    expect(result.current[0]).toEqual([1, 2]);
  });

  it("should retrieve the stored value from local storage", () => {
    localStorageMock.setItem("test", "storedValue");
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    expect(result.current[0]).toBe("storedValue");
  });

  it("should set a new value", () => {
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    act(() => {
      result.current[1]("new_value");
    });
    expect(result.current[0]).toBe("new_value");
    expect(JSON.parse(localStorageMock.getItem("test")!)).toBe("new_value");
  });

  it("should handle functions as new value", () => {
    const { result } = renderHook(() => useLocalStorage("count", 1));

    act(() => {
      result.current[1]((prev) => prev + 1);
    });

    expect(result.current[0]).toBe(2);
    expect(JSON.parse(localStorageMock.getItem("count")!)).toBe("2");
  });

當我們寫完之後,就會發現test發生錯誤拉

https://ithelp.ithome.com.tw/upload/images/20230913/20162289LBDNTcxnYU.png

這就是為什麼我們要寫測試拉

究竟是為什麼呢,就留給明天再說吧,自己也可以練習看看喔
如果自己寫完全沒有出錯的人,代表你很棒喔,測試就是為了怕在寫function時遇到沒預期的錯誤

今天的完整程式碼

import { renderHook, act } from "@testing-library/react";
import { useLocalStorage } from "../src"; // 導入你的 useLocalStorage hook

// 模擬 LocalStorage
class LocalStorageMock {
  store: Record<string, string> = {};

  getItem(key: string) {
    return this.store[key] || null;
  }

  setItem<T>(key: string, value: T) {
    this.store[key] = JSON.stringify(value);
  }

  removeItem(key: string) {
    delete this.store[key];
  }

  clear() {
    this.store = {};
  }
}

describe("useLocalStorage", () => {
  let localStorageMock: LocalStorageMock;
  beforeAll(() => {
    localStorageMock = new LocalStorageMock();
    Object.defineProperty(window, "localStorage", {
      value: localStorageMock,
    });
  });
  beforeEach(() => {
    localStorageMock.clear();
  });

  it("should retrieve the default value when local storage is empty", () => {
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    expect(result.current[0]).toBe("default");
  });

  it("Initial state is a callback function", () => {
    const { result } = renderHook(() => useLocalStorage("key", () => "value"));

    expect(result.current[0]).toBe("value");
  });

  it("Initial state is an array", () => {
    const { result } = renderHook(() => useLocalStorage("digits", [1, 2]));

    expect(result.current[0]).toEqual([1, 2]);
  });

  it("should retrieve the stored value from local storage", () => {
    localStorageMock.setItem("test", "storedValue");
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    expect(result.current[0]).toBe("storedValue");
  });

  it("should set a new value", () => {
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    act(() => {
      result.current[1]("new_value");
    });
    expect(result.current[0]).toBe("new_value");
    expect(JSON.parse(localStorageMock.getItem("test")!)).toBe("new_value");
  });

  it("should handle functions as new value", () => {
    const { result } = renderHook(() => useLocalStorage("test", "default"));

    act(() => {
      result.current[1]((prev) => "new_" + prev);
    });

    expect(result.current[0]).toBe("new_default");
    expect(JSON.parse(localStorageMock.getItem("test")!)).toBe("new_default");
  });

});

抱歉今天這麼晚才出產,最近工作有點忙,我應該先提前存好文章的🥲


上一篇
[Day 3] 本地存取useLocalStorage登場
下一篇
[Day 5] 用第二種測試方式寫useSessionStorage測試
系列文
React進階班,用typescript與jest製作自己的custom hooks庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言