Skip to content

Test & coverage

Core phục vụ nhiều team: một lỗi lọt qua là lỗi ở mọi sản phẩm sau lần nâng cấp. Vì vậy package chứa logic có ngưỡng coverage bắt buộc, và CI fail khi test không đủ.

Chạy test

ViệcLệnh
Toàn bộ, như CIpnpm test && pnpm test:scripts
Một packagepnpm --filter @tasco/ui test
Một filepnpm --filter @tasco/utils test -- src/error.test.ts
Chế độ theo dõi khi đang viếtpnpm --filter @tasco/composables exec vitest

pnpm test chạy qua Turborepo sau ^build, nên package phụ thuộc được build trước. Báo cáo coverage nằm ở packages/<tên>/coverage/ (không commit).

Mỗi package test gì, ngưỡng bao nhiêu

PackageCông cụMôi trườngNgưỡng (dòng / hàm / nhánh / câu lệnh)
@tasco/utilsVitestnode95 / 95 / 95 / 95
@tasco/composablesVitestnode, mock nuxt/app90 / 90 / 85 / 90
@tasco/aiVitestnode90 / 90 / 85 / 90
@tasco/mcpVitestnode90 / 90 / 85 / 90
@tasco/uiVitest + @vue/test-utilshappy-dom85 / 90 / 70 / 85, chỉ tính file trong coverage.include
@tasco/themeVitestnodeCó báo cáo, chưa đặt ngưỡng; test tương phản khoá token
@tasco/nuxt-layer-baseVitestnodeChỉ util thuần trong server/ (session, IAM, redirect), không đo coverage
@tasco/cli, @tasco/eslint-confignode --testnodeTest hợp đồng: template sinh ra, cấu hình ESLint
scripts/, docs/.vitepress/node --testnodedocs-coverage, release-guard, sinh changelog và trang API

File bị loại khỏi coverage nằm trong coverage.exclude của từng vitest.config.ts, mỗi mục có comment lý do (barrel, file chỉ khai kiểu, glue gọi Nuxt/h3 không unit test được). Thêm vào danh sách này phải giải thích trong MR.

@tasco/ui dùng danh sách include: component mới phải được thêm vào thì mới tính coverage — quên thêm nghĩa là component không có ngưỡng nào bảo vệ.

Quy ước

  • File test đặt cạnh file nguồn: useTable.tsuseTable.test.ts.
  • Tên test mô tả hành vi và kết quả: it('status=0 → inactive'), it('không cấu hình baseURL → gọi qua /api').
  • Test hành vi quan sát được (giá trị trả về, class, text, sự kiện phát ra), không test chi tiết cài đặt.
  • Module giữ state nội bộ (vd bộ nhớ chống lặp thông báo) export hàm _reset* chỉ dùng trong test.
  • Tách lõi thuần để test không cần mock — xem Thêm composable & util.

Mock Nuxt

ts
const mocks = vi.hoisted(() => ({
  appConfig: {} as { tasco?: { themeMode?: 'light' | 'dark' } },
}))

vi.mock('nuxt/app', () => ({
  useAppConfig: () => mocks.appConfig,
}))

import { useThemeMode } from './useTheme'

vi.mock được Vitest đưa lên đầu file, trước cả khai báo biến thường — biến dùng trong factory của vi.mock phải tạo bằng vi.hoisted. import module cần test đặt sau vi.mock.

Stub antdv trong test component

ts
vi.mock('ant-design-vue', () => ({
  Tooltip: defineComponent({
    name: 'ATooltip',
    setup: (_props, { slots }) => () => h('span', {}, slots.default?.()),
  }),
}))

import CStatus from './CStatus.vue'

Chỉ stub component antdv mà component đang test dùng tới; stub giữ lại slot để nội dung vẫn render. Không test lại hành vi của antdv.

Test phụ thuộc môi trường Node

  • Hàm đọc file, gọi fetch: nhận fetch hoặc storage qua tham số tuỳ chọn (options.fetch, options.storage) để test truyền bản giả — như useAiChatgetTableSettings.
  • Script .mjs trong scripts/: export hàm thuần, phần main() chỉ chạy khi gọi trực tiếp; test import hàm thuần.

Không có test tự động

  • End-to-end qua trình duyệt: chưa có. Thay đổi giao diện hoặc luồng đăng nhập thì thử tay trên playground và ghi bước thử vào Test plan của MR.
  • Kiểu của app sản phẩm: vue-tsc chỉ chạy trên package, playground và demo của docs. Đổi kiểu công khai (prop, slot, RouteMeta) thì thử pnpm typecheck trên một app tạo từ CLI.

Khi CI báo coverage thấp

  1. Mở coverage/index.html của package, tìm nhánh chưa phủ.
  2. Viết test cho hành vi ở nhánh đó — thường là trường hợp lỗi hoặc giá trị biên.
  3. Không hạ ngưỡng. Nhánh thật sự không test được (glue thuần) thì tách ra file riêng và đưa vào exclude kèm comment.

Liên quan