---
# System prepended metadata

title: 前端單元測試 筆記

---

# 前端單元測試 筆記 

不能測試，所以要重構（把 code 變乾淨)

連線成本高，所以要 mock。每個 mock 手法又是不同的難度。但前後要花好多時間才能走到關鍵。


![](https://i.imgur.com/mJwcKDE.png)

![](https://i.imgur.com/un26znX.png)

為什麼要測試? 
抓到上線會出現的問題，我有信心不加班。sideEffect 有沒有被改壞。

![](https://i.imgur.com/W85sPeu.png)

根據現有的時間，決定要怎麼測試。不用糾結到底要寫多純。

![](https://i.imgur.com/qVWXy5l.png)

# Jest

[jest-watch-typeahead](https://www.npmjs.com/package/jest-watch-typeahead)

test.only 只測試某項
test.skip 跳過某項
# React Testing Library

react-testing library 有提供 [testing-playground](https://testing-playground.com/)，告訴你該怎麼取到 element

取的方式不同，assertion 也會不同。

測試完成要記得回到原本的扣改壞看看。

# snap shot
toMatchSnapshot - 會產生一個資料夾
toMatchInlineSnapshot - 在原本的測試檔案裡面

# userEvent vs fireEvent
userEvent 是站在使用者的角度出發，不管你底層技術是如何實作。例如按按鈕，onMouseDown or onClick 都會達到相同的效果。好處是背後技術換了，測試不用一直改，因為從使用者的角度來看，都是一樣的。


fireEvent 要精準地告訴他，是哪一種事件

# mock
使用 jest timer 不用在原地等三十秒

jest.useFakeTimer()
jest.runAllTimer()
jest.advanceTimerByTime()

# act
react test utilities

等 component 的 lifecycle 重新 render 完成，避免一些 side effect

![](https://i.imgur.com/eZYZxjO.png)


fireEvent 為什麼不用包 act ? 因為 fireEvent and userEvent 已經內建包好了

# styled component
styled component 會產生測試的亂碼，如果 css 有改變，測試就死掉了。但無法透過肉眼辨識快照的差異。

jest-styled-components 透過這個軟件

# screen vs container 

screen 整張 html render 的東西
container 是針對 render(<這個東東/>) render 的東西

```js
  screen.debug()
  debug(container)
  debug(container.firstChild)
```
# subtest & test each （不一樣 data 重複的事情）
每個情境都寫一樣，很不必要。因為只是參數和結果不一樣。


```js
import { screen, render } from "@testing-library/react";
import Button from "../../exercise/06";
import { ButtonType } from "../../exercise/06types";

// before
describe("Button with diferrent type", () => {
  test("with type A", async () => {
    render(<Button type={ButtonType.A} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-green-500");
  });
  test("with type B", async () => {
    render(<Button type={ButtonType.B} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-blue-500");
  });
  test("with type C", async () => {
    render(<Button type={ButtonType.C} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-red-500");
  });

  test("with type X", async () => {
    render(<Button type={"X"} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-green-500");
  });
});
 
```
故意把某個用壞，看不到之後的狀況
```js
import { screen, render } from "@testing-library/react";
import Button from "../../exercise/06";
import { ButtonType } from "../../exercise/06types";

// before
describe("Button with diferrent type", () => {
  test('Button with all types', async() => {
    const {rerender} = render(<Button type={ButtonType.A} />);

    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-green-500");
    rerender(<Button type={ButtonType.B} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-bluess-500");
    rerender(<Button type={ButtonType.C} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-redss-500");
    rerender(<Button type={"X"} />);
    expect(screen.getByText(/hi/i)).toHaveClass("eb-bg-green-500");
  })
});
```

```js
  test.each([[ButtonType.A, "eb-bg-green-500"], 
             [ButtonType.B, "eb-bg-blue-500"],
             [ButtonType.C, "eb-bg-red-500"],
             ["X", "eb-bg-green-500"],
            ],
           )
  ('type %s will have className %s', (type, className) => {
    render(<Button type={type}/>)
    expect(screen.getByText(/hi/i)).toHaveClass(className)
  })

  test.each([
    {type: ButtonType.A, className: "eb-bg-green-500"}, 
    {type: ButtonType.B, className: "eb-bg-blue-500"},
    {type: ButtonType.C, className: "eb-bg-red-500"},
    {type: "X", className: "eb-bg-green-500"}
  ])("type $type will have className $className", ({type, className}) => {
    render(<Button type={type}/>)
    expect(screen.getByText(/hi/i)).toHaveClass(className)
  })

  test.each`
    type            | className
    ${ButtonType.A} | ${"eb-bg-green-500"}
    ${ButtonType.B} | ${"eb-bg-blue-500"}
    ${ButtonType.C} | ${"eb-bg-red-500"}
    ${"X"}             | ${"eb-bg-green-500"}
  `("type $type will have className $className", ({type, className}) => {
    render(<Button type={type}/>)
    expect(screen.getByText(/hi/i)).toHaveClass(className)
  })

```
describe 的層級也可以用 each 來做

# msw
reset handlers 是因為在測試的過程中可能改變初始的定義，所以要將他還原回原本的樣子

waitFor 等待 react component side effect done


findby = getBy + waitFor


# Hook

hook 不能單獨存在，每次寫都要把它包進來

```js
import { render, act } from "@testing-library/react";
import useCounter from "../../exercise/08";

test("should increment counter", () => {
  // gogo
  let result 
  function TestCounter () {
    result = useCounter();
    return null
  }
  render(<TestCounter/>)

  expect(result.count ).toBe(0)

  act(() => {
    result.increment()
  })

  expect(result.count).toBe(1)

});
```


透過 [react-hooks-testing-library](https://react-hooks-testing-library.com/)，來簡化測試 hook 碰到煩人步驟




## 常用
```js
 // import userEvent from '@testing-library/user-event'
 expect(screen.getByRole('button')).toHaveTextContent(content);
 
 jest.mock("../../exercise/09/Trade", () => () => "FakeTradeComponent"); // mock component 
 
expect(screen.getByTestId("sell").textContent).toBe("賣完，得到 $10400");
```

@testing-library/react - within usage

# EX

Trade 裡面有亂數，這邊要額外處理

結構變得單純，可以考慮用快照直接照起來


# 參考資料

* winsome 在學 youtube


# 尾聲
*    ![](https://i.imgur.com/nVTxPPe.png)

* django



