今天的練習項目是透過 Mock Service Worker 模擬 Login 時的 Post API !
首先修改範例程式碼,新增在送出時會執行 postLoginData
函式呼叫 API 後回傳正確或錯誤訊息,因為沒有這隻 API 所以網址路徑是模擬的路徑:
import React, { useState } from "react";
import { message } from "antd";
import axios from "axios";
export const checkNotEmptyString = (value) => {
return value.trim() !== "";
};
const Login = () => {
const [loginData, setLoginData] = useState({
username: "",
password: "",
});
const [isChecked, setIsChecked] = useState(false);
const handleChange = (e) => {
setLoginData({
...loginData,
[e.target.name]: e.target.value,
});
};
const postLoginData = async () => {
try {
const result = await await axios.post(
`http://localhost:3000/login`,
loginData
);
message.success("登入成功");
} catch (error) {
message.error(error.response.data.errorMessage);
}
};
const handleSubmit = () => {
if (
checkNotEmptyString(loginData.username) &&
checkNotEmptyString(loginData.password)
) {
postLoginData();
} else {
message.error("請填寫完整資訊");
}
};
return (
<>
<h2>Login</h2>
<form>
<label htmlFor="username">
使用者名稱:
<input
type="text"
id="username"
name="username"
placeholder="請輸入使用者名稱"
value={loginData.username}
onChange={handleChange}
/>
</label>
<br />
<label htmlFor="password">
密碼:
<input
type="password"
id="password"
name="password"
placeholder="請輸入密碼"
value={loginData.password}
onChange={handleChange}
/>
</label>
<br />
<label htmlFor="agreeRules">
<input
type="checkbox"
id="agreeRules"
onChange={(e) => setIsChecked(e.target.checked)}
/>
確認同意網站規則嗎?
</label>
<br />
<button disabled={!isChecked} type="button" onClick={handleSubmit}>
登入
</button>
</form>
</>
);
};
export default Login;
status
回傳兩百時是否正確顯示。status
不是兩百時是否正確顯示。今天透過 Mock Service Worker 來進行模擬 API 回傳值:
如果想學習如何使用 Mock Service Worker ,可以往前幾天的文章看唷!
這邊先簡單復述一次流程:
src/setupTests.js
資料夾全域設定測試時 server 的啟用今天透過 Login 來模擬兩種情境:
handler.js
透過 post 方法,並貼上呼叫 API 的網址後回傳 status 200
import { rest } from "msw";
export const handlers = [
rest.post("http://localhost:3000/login", (req, res, ctx) => {
return res(
ctx.status(200)
);
}),
];
測試的部分跟昨天的送出成功的流程幾乎一樣,並沒有因為程式碼調整而更動,因為都是在測試登入成功後是否能有彈跳視窗提醒成功,僅是將彈出視窗內容改為登入成功!
Login.test.js
test("Successful login", async () => {
render(<Login />);
const nameInputNode = screen.getByLabelText("使用者名稱:");
const passwordInputNode = screen.getByLabelText("密碼:");
const checkbox = screen.getByRole("checkbox");
const loginButton = screen.getByRole("button", { name: "登入" });
userEvent.type(nameInputNode, "艾草");
userEvent.type(passwordInputNode, "a12345678");
userEvent.click(checkbox);
userEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText("登入成功")).toBeInTheDocument();
});
});
在要建立失敗情境時推薦來看官方文件的 Mocking error responses ,範例的部分也是主要從這邊去修改,裡面有提到建議不要直接拋出錯誤訊息,透過回傳 responese
,來區分能錯誤是不是自己預期的:
When it comes to mocking an error response, it's recommended to compose a valid response using
res()
composition chain, rather than throwing an exception inside a request handler. This is mainly to distinguish between internal
and intended
exceptions.
handler.js
可透過 req
取出傳送的值,並透過 ctx.json
回傳模擬的錯誤訊息:
import { rest } from "msw";
export const handlers = [
rest.post("http://localhost:3000/login", async (req, res, ctx) => {
const { username } = await req.json();
return res(
ctx.status(401),
ctx.json({
errorMessage: `該員 ${username} 為我們網站的黑名單,招到通緝!!!不給進!! `,
}),
);
}),
];
Login.test.js
test("Login failed", async () => {
render(<Login />);
const nameInputNode = screen.getByLabelText("使用者名稱:");
const passwordInputNode = screen.getByLabelText("密碼:");
const checkbox = screen.getByRole("checkbox");
const loginButton = screen.getByRole("button", { name: "登入" });
userEvent.type(nameInputNode, "艾草");
userEvent.type(passwordInputNode, "a12345678");
userEvent.click(checkbox);
userEvent.click(loginButton);
await waitFor(() => {
expect(
screen.getByText("該員 艾草 為我們網站的黑名單,招到通緝!!!不給進!!")
).toBeInTheDocument();
});
});
這樣就順利測試兩種情境囉!