# 前端單元測試 筆記
不能測試,所以要重構(把 code 變乾淨)
連線成本高,所以要 mock。每個 mock 手法又是不同的難度。但前後要花好多時間才能走到關鍵。


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

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

# 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

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
# 尾聲
* 
* django