iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
自我挑戰組

<< 測試魔法 >> 這能動嗎?不然就測測看好了!系列 第 28

Login 測試(三):透過 Mock Service Worker 模擬 Post API

  • 分享至 

  • xImage
  •  

今天的練習項目是透過 Mock Service Worker 模擬 Login 時的 Post API !

Login 測試

首先修改範例程式碼,新增在送出時會執行 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;

測試內容

  • API status 回傳兩百時是否正確顯示。
  • API status 不是兩百時是否正確顯示。

今天透過 Mock Service Worker 來進行模擬 API 回傳值:

如果想學習如何使用 Mock Service Worker ,可以往前幾天的文章看唷!

Mock Service Worker 流程復述

這邊先簡單復述一次流程:

  1. 下載它
  2. 新增 mocks 資料夾
  3. 裡面新增 handler.js
  4. 因為採用 Node 的方式所以要再新增 mocks 資料夾內新增 server.js 檔案,並配置模擬 server
  5. 透過 create react app 建造的專案可在 src/setupTests.js 資料夾全域設定測試時 server 的啟用
  6. 在 handler.js 新增想模擬的 API 及其回傳值

今天透過 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();
  });
});

這樣就順利測試兩種情境囉!


參考文章

https://mswjs.io/docs


上一篇
Login 測試(二):透過 Submit 介紹何為私有函式
下一篇
學習測試的好處
系列文
<< 測試魔法 >> 這能動嗎?不然就測測看好了!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言