使用vitest+testing library做vite构建的react项目前端单元测试(含覆盖率统计)

原创
2023/06/09 09:08
阅读数 118

使用的库package.json

  "scripts": {
    "test": "vitest --ui --coverage",
  }
  "devDependencies": {
    "@testing-library/jest-dom": "^5.16.5", --测试工具包
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.4.3",
    "@vitest/coverage-v8": "latest", --覆盖率工具
    "@vitest/ui": "latest", --测试结果页面展示
    "jsdom": "^22.1.0",
    "msw": "^1.2.1", --mock后端API工具
    "vitest": "latest" --核心测试框架
  }

vite.config.ts配置

/// <reference types="vitest"/>

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from "path"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve:{
    alias:{
      "@":path.resolve(__dirname,'./src')
    }
  },
  test:{
    globals:true,
    environment:"jsdom",
    setupFiles:['./src/__test__/setup.ts'],
    css:false,
    coverage:{
      provider:"v8",
      reporter:['html']
    }
  }
})


setup.ts

import { rest } from "msw";
import { setupServer } from "msw/node";
import {beforeAll,afterEach,afterAll} from 'vitest'
import "@testing-library/jest-dom"
import { cleanup } from "@testing-library/react";

export const server = setupServer(
    //默认mock的api返回值
    rest.get("http://127.0.0.1:8000/api/v1/records/pages", (_req, res, ctx) => {
      return res(
        ctx.status(200),
        ctx.json([
          {
            id: 1,
            category: "1",
            amount: 1,
            registrationDate: "2023-06-06",
            type: "1",
          },
          {
            id: 2,
            category: "2",
            amount: 1,
            registrationDate: "2023-05-06",
            type: "2",
          },
        ])
      );
    }),
    rest.get("http://127.0.0.1:8000/api/v1/records/pages/:date", (req, res, ctx) => {
      const date = req.params.date
      if(date === '2023-05'){
        return res(
          ctx.status(200),
          ctx.json([
            {
              id: 2,
              category: "1",
              amount: 1,
              registrationDate: "2023-05-06",
              type: "1",
            },
          ])
        );
      }else{
        return res(ctx.status(500));
      }
      
    }),
    rest.post("http://127.0.0.1:8000/api/v1/records/:id", (_req, res, ctx) => {
      return res.once(
        ctx.status(200),
      );
    }),
    rest.post("http://127.0.0.1:8000/api/v1/records", (_req, res, ctx) => {
      return res.once(
        ctx.status(200),
      );
    }),
  );

beforeAll(() => server.listen({onUnhandledRequest:'error'}));
afterAll(() => server.close());
beforeEach(() => {
  cleanup();
});
afterEach(() => {
  server.resetHandlers();
  cleanup();
});

单元测试文件Test.test.tsx

import {
  render,
  screen,
  fireEvent,
  act,
} from "@testing-library/react";
import Home from "@/views/Home";
import { rest } from "msw";
import { server } from "./setup";
import { expect } from "vitest";
import App from "@/App";
import { BrowserRouter } from "react-router-dom";

it("test save button disable", async () => {
  render(<Home />);
  const button = screen.getByText("保存");
  await act(() => { //wait request be processed,因react是异步渲染,需要等待渲染完成才可继续
    fireEvent.click(button);
  });
  expect(button).toHaveAttribute("disabled");
});

it("test category change", async () => {
  //这里测选项,因选择框超出组件本身范围,需要拿到App层级的对象来查找
  const {container, getByRole} = render(<App />, {wrapper: BrowserRouter});
  //default is income category
  const button = container.querySelector("#select-item") as HTMLElement;
  fireEvent.mouseDown(button);
  let list =await getByRole("listbox");
  expect(list.children).toHaveLength(8);
  act(() => {
    const options=list.querySelectorAll("li")[0] as HTMLElement;
    options.click();
  });
  expect(button).toHaveTextContent("1");
  //change to outcome
  const outButton = screen.getByText("支出");
  fireEvent.click(outButton);
  fireEvent.mouseDown(button);
  list =await getByRole("listbox");
  expect(list.children).toHaveLength(12);
  act(() => {
    const options=list.querySelectorAll("li")[0] as HTMLElement;
    options.click();
  });
  expect(button).toHaveTextContent("食費");
});

it('test amount input', () => {
  const container = render(<Home />).container;
  const amount = container.querySelector("#amount-textField") as HTMLElement;
  fireEvent.change(amount, { target: { value: "" } });
  //1以上数値。
  expect(container).toHaveTextContent("1以上数値。");
  fireEvent.change(amount, { target: { value: "11111111111" } });
  //10位以下数値。
  expect(container).toHaveTextContent("10位以下数値。");
});

it('test save success', async () => {
  const {container, getByRole} = render(<App />, {wrapper: BrowserRouter});
  //set category
  const button = container.querySelector("#select-item") as HTMLElement;
  fireEvent.mouseDown(button);
  const list =await getByRole("listbox");
  expect(list.children).toHaveLength(8);
  act(() => {
    const options=list.querySelectorAll("li")[0] as HTMLElement;
    options.click();
  });
  expect(button).toHaveTextContent("給与");
  //set amount
  const amount = container.querySelector("#amount-textField") as HTMLElement;
  fireEvent.change(amount, { target: { value: "1" } });
  //set date
  const dateInput = container.querySelector("#dayYear-textField") as HTMLElement;
  fireEvent.change(dateInput, { target: { value: "2023-06-14" } });
  //save button
  const saveButton = screen.getByText("保存");
  await act(() => { //wait request be processed
    fireEvent.click(saveButton);
  });
  //保存成功。
  expect(container).toHaveTextContent("保存成功。");
});

it('test save failed', async () => {
  const {container, getByRole} = render(<App />, {wrapper: BrowserRouter});
  //set category
  const button = container.querySelector("#select-item") as HTMLElement;
  fireEvent.mouseDown(button);
  const list =await getByRole("listbox");
  expect(list.children).toHaveLength(8);
  act(() => {
    const options=list.querySelectorAll("li")[0] as HTMLElement;
    options.click();
  });
  expect(button).toHaveTextContent("1");
  //set amount
  const amount = container.querySelector("#amount-textField") as HTMLElement;
  fireEvent.change(amount, { target: { value: "1" } });
  //set date
  const dateInput = container.querySelector("#dayYear-textField") as HTMLElement;
  fireEvent.change(dateInput, { target: { value: "2023-06-14" } });
  //save button
  const saveButton = screen.getByText("保存");
  server.use(
    rest.post("http://127.0.0.1:8000/api/v1/records", (_req, res, ctx) => {
      return res.once(
        ctx.status(500),
        ctx.json([
          {
            code: "Internal ERROR",
          },
        ])
      );
    })
  );
  await act(() => {
    fireEvent.click(saveButton);
  });
  //保存failed。
  expect(container).toHaveTextContent("CODE:");
});

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部