Một CLI viết bằng JavaScript và một ứng dụng React dùng Vite đều cần unit test. Tuy nhiên, chúng có cách nạp module, biến đổi source và mô phỏng môi trường khác nhau. Chọn runner chỉ từ số lượt tải package dễ làm team phải bù cấu hình ở những phần quan trọng nhất.
Bài đánh giá này dựa trên tài liệu chính thức và một ví dụ nhỏ, không có benchmark hai công cụ. Mốc đối chiếu là Node.js 24.18.1 và tài liệu Vitest tại ngày 23/10/2026. Trang hướng dẫn Vitest được đọc đang yêu cầu Node từ 22.12.0 và Vite từ 6.4.0; cần kiểm tra lại yêu cầu của bản bạn thực sự cài và lưu lockfile.
Bắt đầu từ môi trường thực thi
node:test được cung cấp cùng Node.js. Với một file .mjs chỉ dùng JavaScript chuẩn, bạn có thể viết test mà không cài thêm framework. Điều này thuận tiện cho utility, CLI hoặc service có đường nạp module gần với môi trường Node triển khai thực tế.
Vitest dùng hệ thống của Vite để xử lý source, resolver và plugin. Nếu ứng dụng đã phụ thuộc alias, JSX hoặc cách biến đổi TypeScript của Vite, việc dùng chung cấu hình có thể giảm khoảng cách giữa build và test. Nó không tự bảo đảm rằng mọi hành vi của trình duyệt hay thiết bị native đã được mô phỏng chính xác.
| Tiêu chí | Node test runner | Vitest |
|---|---|---|
| Bắt đầu với JavaScript thuần | Có sẵn trong Node tương ứng | Cài dependency cho dự án |
| Đường nạp module | Theo khả năng runtime và cấu hình Node | Qua hệ thống biến đổi, resolver của Vite |
| Assertion thường dùng | node:assert/strict |
expect và matcher theo hệ sinh thái Vitest |
| Mock | API mock tích hợp, cần kiểm tra mức ổn định từng tính năng | API mock theo tài liệu Vitest, có khác biệt theo môi trường |
| Kiểm tra UI web | Cần chọn thêm môi trường và công cụ | Có hỗ trợ môi trường DOM giả lập hoặc Browser Mode khi cấu hình |
| Hiệu năng dự án của bạn | Cần đo | Cần đo |
Một ví dụ đủ nhỏ để so cách viết
Tạo tags.node.test.mjs trong một thư mục thử riêng rồi chạy bằng lệnh phía dưới. Test kiểm tra cách chuẩn hóa khoảng trắng và loại tag trùng, không truy cập mạng hay database.
import test from 'node:test';
import assert from 'node:assert/strict';
const normalize = tags => [...new Set(tags.map(x => x.trim()).filter(Boolean))];
test('normalizes tags while preserving first occurrence order', () => {
assert.deepEqual(normalize([' AI ', '', 'AI', 'Testing']), ['AI', 'Testing']);
});
node --version
node --test tags.node.test.mjs
Một cách viết tương đương trong Vitest như sau. Đặt ví dụ thay thế này ở thư mục khác với ví dụ Node, tránh để một runner tự quét cả hai loại file. Trong dự án thực, tách hàm cần kiểm tra thành module rồi dùng chung các ca đầu vào.
import { test, expect } from 'vitest';
const normalize = tags => [...new Set(tags.map(x => x.trim()).filter(Boolean))];
test('normalizes tags while preserving first occurrence order', () => {
expect(normalize([' AI ', '', 'AI', 'Testing'])).toEqual(['AI', 'Testing']);
});
Lệnh chạy một lượt của Vitest là vitest run, thông thường gọi qua script trong package.json. Chế độ watch phù hợp khi phát triển nhưng không nên làm pipeline chờ vô hạn. Với cả hai runner, kiểm tra rằng lệnh thực sự phát hiện đúng file và số test; một job xanh vì không tìm thấy ca nào không chứng minh code đúng.
Chi phí thường nằm ngoài câu assertion
Nếu source nhập đường dẫn @/services/cart, hãy kiểm tra resolver của test có hiểu alias giống build không. Nếu test cần DOM, hãy chọn rõ môi trường; có object document từ thư viện giả lập chưa chứng minh tương tác sẽ đúng trên trình duyệt thật. Với React Native, unit test bằng runner JavaScript cũng không thay thế kiểm tra Android và iOS.
Mock module và mock timer cần được rà kỹ khi chuyển runner. Cách nạp ESM, thời điểm import và vòng đời khôi phục mock ảnh hưởng đến kết quả. Không đổi tên jest thành vi trên toàn dự án rồi coi việc chuyển đổi đã hoàn tất. Hãy lấy vài ca có side effect, timer và lỗi async làm bộ thử chuyển đổi đầu tiên.
Chi phí vận hành còn gồm dependency, cập nhật lockfile, coverage và cách nhóm test trong CI. Runner tích hợp vẫn cần cập nhật Node; framework cài thêm cần theo dõi cả yêu cầu runtime lẫn các package phục vụ biến đổi source. Không lựa chọn nào có chi phí bảo trì bằng không.
Chọn bằng một thử nghiệm của chính repo
Lấy khoảng mười ca đại diện của dự án: một utility đồng bộ, một lỗi bất đồng bộ, một module có alias và một nhóm có mock. Ghi lại cấu hình cần thêm, chất lượng thông báo lỗi, khả năng chạy đúng trong CI và thời gian trên cùng máy. Tách thời gian cài dependency khỏi thời gian chạy test để tránh so hai công việc khác nhau.
Với CLI JavaScript thuần, hãy thử runner tích hợp trước nếu nó đáp ứng các ca này. Với ứng dụng Vite đã có nhiều TypeScript, JSX và alias, Vitest thường đáng thử trước vì cách nạp source gần với build. Đây là quyết định theo cấu trúc dự án, không phải kết luận công cụ nào luôn tốt hơn.
Giới hạn đánh giá
Ví dụ Node được kiểm tra bằng Node 24.18.1 trong lượt biên tập. Ví dụ Vitest được đối chiếu tài liệu nhưng chưa thực thi trong một dự án đã cài Vitest; bài không công bố số đo tốc độ, mức tiêu thụ RAM hay kết quả tương thích của repo thực tế. Những dữ liệu đó cần thử bằng chính lockfile và cấu hình của team.




