- การทดสอบ Unit Test ด้วย Jest
- 2. Jest คืออะไร
- 3. เตรียม Project
- 4. ตั้งค่า package.json
- 5. สร้าง Unit Test แรก
- 6. การใช้ describe()
- 7. Matchers ที่ใช้บ่อย
- 8. ทดสอบ Error
- 9. Setup และ Teardown
- 10. beforeAll()
- 11. beforeEach()
- 12. afterEach() และ afterAll()
- 13. การทดสอบ Asynchronous Function
- 14. resolves
- 15. rejects
- 16. Mock Function
- 17. ตรวจสอบจำนวนครั้งที่ Function ถูกเรียก
- 18. ตรวจสอบ Argument
- 19. Mock Return Value
- 20. Mock Promise
- 21. Dependency Injection กับ Unit Test
- 22. Mock Module
- 23. Code Coverage
- 24. การตั้งค่า Coverage Threshold
- 25. การรัน Test เฉพาะไฟล์
- 26. รัน Test ตามชื่อ
- 27. Watch Mode
- 28. การจัด Folder สำหรับโครงการขนาดเล็ก
- 29. การจัด Folder สำหรับโครงการขนาดกลางหรือใหญ่
- 30. Naming Convention
- 31. Arrange – Act – Assert
- 32. ตัวอย่าง Service Test ที่สมจริงขึ้น
- 33. Unit Test ต่างจาก Integration Test อย่างไร
- 34. Unit Test ที่ดีควรมีลักษณะอย่างไร
- 35. สิ่งที่ไม่ควรทำ
- 36. Jest กับ CI/CD
- 37. Workflow แนะนำ
- 38. คำสั่ง Jest ที่ควรรู้
- 39. Checklist สำหรับ Unit Test
- 40. สรุป
#การทดสอบ Unit Test ด้วย Jest
Jest เป็น JavaScript Testing Framework ที่นิยมใช้สำหรับการทำ Unit Testing และสามารถนำไปใช้กับโครงการ JavaScript, Node.js, React และ TypeScript ได้
แนวคิดของ Unit Test คือการทดสอบหน่วยเล็กที่สุดของโปรแกรม เช่น ฟังก์ชัน เมธอด หรือโมดูล โดยพยายามแยกส่วนที่กำลังทดสอบออกจาก Database, API, File System หรือบริการภายนอก เพื่อให้การทดสอบทำงานได้เร็วและตรวจสอบข้อผิดพลาดได้ตรงจุด
ณ เดือนกันยายน 2026 เอกสารทางการของ Jest ระบุเวอร์ชัน Stable เป็น Jest 30.5 โดย Jest 30 รองรับ Node.js ตั้งแต่เวอร์ชัน 18 ขึ้นไป
#1. ทำไมต้องทำ Unit Testing
Unit Test ช่วยให้ทีมพัฒนาซอฟต์แวร์สามารถตรวจสอบพฤติกรรมของโค้ดได้โดยอัตโนมัติ เช่น
- ตรวจสอบว่าฟังก์ชันให้ผลลัพธ์ถูกต้อง
- ตรวจสอบกรณีปกติและกรณีผิดพลาด
- ลดความเสี่ยงเมื่อ Refactor โค้ด
- ช่วยตรวจจับ Regression
- ใช้ร่วมกับ CI/CD Pipeline ได้
- ทำให้ Developer กล้าแก้ไขโค้ดมากขึ้น เพราะมี Test ช่วยตรวจสอบ
ตัวอย่างเช่น เรามีฟังก์ชันคำนวณผลรวม
function add(a, b) {
return a + b;
}
เราสามารถเขียน Unit Test เพื่อตรวจสอบได้ว่า
add(2, 3)
ต้องได้ผลลัพธ์เป็น
5
#2. Jest คืออะไร
Jest เป็น Testing Framework สำหรับ JavaScript ที่รวมเครื่องมือสำคัญไว้ในชุดเดียว เช่น
- Test Runner
- Assertion / Matcher
- Mock Function
- Module Mocking
- Snapshot Testing
- Code Coverage
- Watch Mode
รูปแบบพื้นฐานของ Test ใน Jest คือ
test('คำอธิบายสิ่งที่ต้องการทดสอบ', () => {
expect(actual).toBe(expected);
});
หรือสามารถใช้ it() แทน test() ได้
it('should return 4', () => {
expect(2 + 2).toBe(4);
});
#3. เตรียม Project
สร้างโครงการ Node.js
mkdir jest-unit-testing
cd jest-unit-testing
สร้าง package.json
npm init -y
ติดตั้ง Jest
npm install --save-dev jest
ตรวจสอบเวอร์ชัน
npx jest --version
#4. ตั้งค่า package.json
เพิ่มคำสั่งสำหรับรัน Test
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
จากนั้นสามารถรัน Test ด้วย
npm test
หรือ
npm run test:coverage
#5. สร้าง Unit Test แรก
สร้างโครงสร้างไฟล์
jest-unit-testing/
├── src/
│ └── calculator.js
├── tests/
│ └── calculator.test.js
├── package.json
└── node_modules/
ไฟล์
src/calculator.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
module.exports = {
add,
subtract,
multiply,
divide,
};
สร้างไฟล์ Test
tests/calculator.test.js
const {
add,
subtract,
multiply,
divide,
} = require('../src/calculator');
test('2 + 3 should equal 5', () => {
expect(add(2, 3)).toBe(5);
});
test('10 - 4 should equal 6', () => {
expect(subtract(10, 4)).toBe(6);
});
test('3 * 4 should equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('10 / 2 should equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
รัน Test
npm test
ตัวอย่างผลลัพธ์
PASS tests/calculator.test.js
✓ 2 + 3 should equal 5
✓ 10 - 4 should equal 6
✓ 3 * 4 should equal 12
✓ 10 / 2 should equal 5
#6. การใช้ describe()
เมื่อมีหลาย Test ที่เกี่ยวข้องกัน สามารถจัดกลุ่มด้วย describe()
const {
add,
subtract,
} = require('../src/calculator');
describe('Calculator', () => {
test('should add two numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('should subtract two numbers', () => {
expect(subtract(10, 4)).toBe(6);
});
});
โครงสร้างจะอ่านง่ายขึ้น
Calculator
✓ should add two numbers
✓ should subtract two numbers
#7. Matchers ที่ใช้บ่อย
Jest ใช้ expect() ร่วมกับ Matcher เพื่อระบุผลลัพธ์ที่คาดหวัง
#toBe()
เหมาะกับ Primitive Value
expect(2 + 2).toBe(4);
#toEqual()
เหมาะกับ Object และ Array
const user = {
id: 1,
name: 'Alice',
};
expect(user).toEqual({
id: 1,
name: 'Alice',
});
#toStrictEqual()
ตรวจสอบโครงสร้างและชนิดของ Object อย่างเข้มงวดกว่า toEqual()
expect({
id: 1,
name: 'Alice',
}).toStrictEqual({
id: 1,
name: 'Alice',
});
#toBeTruthy()
expect(true).toBeTruthy();
#toBeFalsy()
expect(false).toBeFalsy();
#toBeNull()
expect(null).toBeNull();
#toBeUndefined()
let value;
expect(value).toBeUndefined();
#toContain()
เหมาะกับ Array หรือ String
const languages = [
'JavaScript',
'TypeScript',
'Python',
];
expect(languages).toContain('JavaScript');
#toMatch()
ตรวจสอบ String ด้วย String หรือ Regular Expression
expect('software testing').toMatch(/testing/);
#toThrow()
ใช้ตรวจสอบ Exception
expect(() => {
throw new Error('Something went wrong');
}).toThrow();
สามารถตรวจข้อความ Error ได้
expect(() => {
throw new Error('Something went wrong');
}).toThrow('Something went wrong');
#8. ทดสอบ Error
จากฟังก์ชัน
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
สามารถเขียน Test ได้ดังนี้
test('should throw error when dividing by zero', () => {
expect(() => divide(10, 0))
.toThrow('Cannot divide by zero');
});
#9. Setup และ Teardown
Jest มี Lifecycle Hooks สำหรับเตรียมและคืนค่าทรัพยากรก่อนหรือหลัง Test
ประกอบด้วย
beforeAll()
beforeEach()
afterEach()
afterAll()
ตัวอย่าง
let counter;
beforeEach(() => {
counter = 0;
});
test('counter should start from zero', () => {
expect(counter).toBe(0);
});
#10. beforeAll()
ทำงานหนึ่งครั้งก่อน Test ทั้งหมดใน Test Suite
let users;
beforeAll(() => {
users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
});
test('should contain two users', () => {
expect(users).toHaveLength(2);
});
#11. beforeEach()
ทำงานก่อน Test แต่ละตัว
let cart;
beforeEach(() => {
cart = [];
});
test('cart should be empty', () => {
expect(cart).toHaveLength(0);
});
#12. afterEach() และ afterAll()
ใช้สำหรับ Cleanup Resource เช่น
- Reset Mock
- ปิด Connection
- ลบข้อมูลชั่วคราว
- คืนค่า Environment
ตัวอย่าง
afterEach(() => {
jest.clearAllMocks();
});
#13. การทดสอบ Asynchronous Function
สมมติว่ามีฟังก์ชัน
async function getUser() {
return {
id: 1,
name: 'Alice',
};
}
module.exports = {
getUser,
};
สามารถทดสอบด้วย async/await
const {
getUser,
} = require('../src/userService');
test('should return user', async () => {
const user = await getUser();
expect(user).toEqual({
id: 1,
name: 'Alice',
});
});
#14. resolves
Jest สามารถตรวจสอบ Promise ด้วย resolves
test('should resolve user', async () => {
await expect(getUser()).resolves.toEqual({
id: 1,
name: 'Alice',
});
});
#15. rejects
ใช้ทดสอบ Promise ที่ถูก Reject
async function login(username, password) {
if (!username || !password) {
throw new Error('Username and password are required');
}
return true;
}
Test
test('should reject when credential is missing', async () => {
await expect(
login('', '')
).rejects.toThrow(
'Username and password are required'
);
});
#16. Mock Function
ใน Unit Test เรามักไม่ต้องการเรียก Dependency จริง เช่น
- External API
- Database
- Email Service
- Payment Gateway
- File System
Jest สามารถสร้าง Mock Function ด้วย
jest.fn()
ตัวอย่าง
test('should call callback', () => {
const callback = jest.fn();
callback('hello');
expect(callback).toHaveBeenCalled();
});
#17. ตรวจสอบจำนวนครั้งที่ Function ถูกเรียก
const mockFunction = jest.fn();
mockFunction();
mockFunction();
expect(mockFunction)
.toHaveBeenCalledTimes(2);
#18. ตรวจสอบ Argument
const sendEmail = jest.fn();
sendEmail(
'user@example.com',
'Welcome'
);
expect(sendEmail)
.toHaveBeenCalledWith(
'user@example.com',
'Welcome'
);
#19. Mock Return Value
กำหนดค่าที่ Mock Function ต้องคืน
const getUser = jest.fn();
getUser.mockReturnValue({
id: 1,
name: 'Alice',
});
expect(getUser()).toEqual({
id: 1,
name: 'Alice',
});
#20. Mock Promise
กรณี Async Function
const getUser = jest.fn();
getUser.mockResolvedValue({
id: 1,
name: 'Alice',
});
Test
test('should return mocked user', async () => {
const user = await getUser();
expect(user.name).toBe('Alice');
});
#21. Dependency Injection กับ Unit Test
วิธีที่ดีในการเขียน Unit Test คือออกแบบโค้ดให้ Dependency สามารถถูกแทนด้วย Mock ได้
ตัวอย่าง
function createUserService(userRepository) {
return {
async findUser(id) {
return userRepository.findById(id);
},
};
}
module.exports = {
createUserService,
};
Test
const {
createUserService,
} = require('../src/userService');
test('should return user from repository', async () => {
const repository = {
findById: jest.fn()
.mockResolvedValue({
id: 1,
name: 'Alice',
}),
};
const userService =
createUserService(repository);
const user =
await userService.findUser(1);
expect(repository.findById)
.toHaveBeenCalledWith(1);
expect(user).toEqual({
id: 1,
name: 'Alice',
});
});
จุดสำคัญคือ Unit Test ไม่จำเป็นต้องเชื่อมต่อ Database จริง
#22. Mock Module
สมมติว่ามีไฟล์
src/emailService.js
function sendEmail(email, message) {
console.log(
`Send "${message}" to ${email}`
);
}
module.exports = {
sendEmail,
};
และ
src/userService.js
const emailService =
require('./emailService');
function registerUser(email) {
emailService.sendEmail(
email,
'Welcome'
);
return {
email,
};
}
module.exports = {
registerUser,
};
สามารถ Mock Module ได้
jest.mock('../src/emailService');
const emailService =
require('../src/emailService');
const {
registerUser,
} = require('../src/userService');
test('should send welcome email', () => {
registerUser(
'user@example.com'
);
expect(
emailService.sendEmail
).toHaveBeenCalledWith(
'user@example.com',
'Welcome'
);
});
#23. Code Coverage
Code Coverage ช่วยตรวจสอบว่า Test ครอบคลุม Source Code มากน้อยเพียงใด
รันด้วย
npm test -- --coverage
หรือ
npx jest --coverage
ผลลัพธ์จะมีตัวชี้วัดหลัก
Statements
Branches
Functions
Lines
ตัวอย่าง
----------------|----------|----------|----------|----------
File | % Stmts | % Branch | % Funcs | % Lines
----------------|----------|----------|----------|----------
calculator.js | 100 | 100 | 100 | 100
----------------|----------|----------|----------|----------
#24. การตั้งค่า Coverage Threshold
สามารถกำหนดเกณฑ์ขั้นต่ำได้ใน
jest.config.js
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.js',
'!src/index.js',
],
coverageDirectory: 'coverage',
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
หาก Coverage ต่ำกว่าเกณฑ์ Jest จะทำให้ Test Run ล้มเหลว ซึ่งเหมาะสำหรับใช้เป็น Quality Gate ใน CI/CD
#25. การรัน Test เฉพาะไฟล์
npx jest calculator.test.js
หรือ
npm test -- calculator.test.js
#26. รัน Test ตามชื่อ
npx jest -t "should add two numbers"
#27. Watch Mode
Watch Mode เหมาะสำหรับ Development
npm run test:watch
หรือ
npx jest --watch
Jest จะตรวจสอบการเปลี่ยนแปลงของไฟล์และรัน Test ที่เกี่ยวข้องอีกครั้ง
#28. การจัด Folder สำหรับโครงการขนาดเล็ก
สามารถเก็บ Test ไว้ใกล้ Source Code
src/
├── calculator.js
├── calculator.test.js
├── userService.js
└── userService.test.js
ข้อดีคือสามารถค้นหา Source และ Test ที่เกี่ยวข้องกันได้ง่าย
#29. การจัด Folder สำหรับโครงการขนาดกลางหรือใหญ่
สามารถแยก Test ออกจาก Source
project/
├── src/
│ ├── services/
│ │ ├── userService.js
│ │ └── paymentService.js
│ ├── repositories/
│ │ └── userRepository.js
│ └── utils/
│ └── calculator.js
│
├── tests/
│ └── unit/
│ ├── services/
│ │ ├── userService.test.js
│ │ └── paymentService.test.js
│ ├── repositories/
│ └── utils/
│ └── calculator.test.js
│
├── jest.config.js
└── package.json
#30. Naming Convention
นิยมตั้งชื่อ Test File เช่น
calculator.test.js
userService.test.js
auth.test.js
หรือ
calculator.spec.js
userService.spec.js
ตัวอย่างชื่อ Test ที่ดี
test(
'should return user when user exists',
() => {
// ...
}
);
ชื่อ Test ควรสื่อสาร
เงื่อนไข → การกระทำ → ผลลัพธ์ที่คาดหวัง
#31. Arrange – Act – Assert
แนวทางที่ช่วยให้ Test อ่านง่ายคือ
Arrange
Act
Assert
ตัวอย่าง
test('should calculate total price', () => {
// Arrange
const price = 100;
const quantity = 3;
// Act
const total =
price * quantity;
// Assert
expect(total).toBe(300);
});
#32. ตัวอย่าง Service Test ที่สมจริงขึ้น
Source
function createOrderService(
productRepository
) {
return {
async calculateTotal(
productId,
quantity
) {
const product =
await productRepository
.findById(productId);
if (!product) {
throw new Error(
'Product not found'
);
}
if (quantity <= 0) {
throw new Error(
'Invalid quantity'
);
}
return (
product.price *
quantity
);
},
};
}
module.exports = {
createOrderService,
};
Test
const {
createOrderService,
} = require(
'../src/orderService'
);
describe('OrderService', () => {
test(
'should calculate total',
async () => {
const repository = {
findById: jest.fn()
.mockResolvedValue({
id: 1,
price: 100,
}),
};
const service =
createOrderService(
repository
);
const result =
await service
.calculateTotal(
1,
3
);
expect(
repository.findById
).toHaveBeenCalledWith(1);
expect(result)
.toBe(300);
}
);
test(
'should throw when product not found',
async () => {
const repository = {
findById: jest.fn()
.mockResolvedValue(null),
};
const service =
createOrderService(
repository
);
await expect(
service.calculateTotal(
100,
1
)
).rejects.toThrow(
'Product not found'
);
}
);
});
#33. Unit Test ต่างจาก Integration Test อย่างไร
| ประเด็น | Unit Test | Integration Test |
|---|---|---|
| ขอบเขต | Function / Class / Module | หลาย Component |
| Dependency | มักใช้ Mock | มักใช้ของจริง |
| Database | ไม่จำเป็น | อาจใช้จริง |
| API | Mock | อาจเรียกจริง |
| ความเร็ว | เร็วมาก | ช้ากว่า |
| Debug | หาสาเหตุง่าย | ซับซ้อนกว่า |
Unit Test ควรเน้น Logic ของ Component เดียว
Integration Test ควรตรวจสอบว่าหลาย Component ทำงานร่วมกันได้ถูกต้อง
#34. Unit Test ที่ดีควรมีลักษณะอย่างไร
แนวคิดที่นิยมคือ FIRST
F = Fast
I = Independent
R = Repeatable
S = Self-validating
T = Timely
#Fast
Test ควรทำงานเร็ว
ไม่ควรรอ
- Network
- External API
- Database จริง
หากไม่จำเป็น
#Independent
แต่ละ Test ไม่ควรพึ่ง Test อื่น
ไม่ควรออกแบบแบบ
Test 2 ต้องรอข้อมูลจาก Test 1
#Repeatable
รันเวลาใดก็ได้และควรได้ผลลัพธ์เหมือนเดิม
#Self-validating
Test ควรตัดสิน Pass หรือ Fail อัตโนมัติ
#Timely
ควรเขียน Test พร้อมกับการพัฒนา Feature หรือใกล้เคียงกับเวลาที่พัฒนา
#35. สิ่งที่ไม่ควรทำ
#1. Test Implementation Detail มากเกินไป
ควร Test Behavior ของ Function มากกว่าโครงสร้างภายใน
#2. Mock ทุกอย่าง
การ Mock มากเกินไปอาจทำให้ Test ผ่านแต่ระบบจริงทำงานผิด
Mock เฉพาะ Dependency ภายนอกหรือสิ่งที่ทำให้ Test ไม่เป็น Unit Test
#3. Test หลาย Behavior ใน Test เดียว
ไม่ควรเขียน
test(
'create update delete user',
() => {
// ...
}
);
ควรแยกเป็น
should create user
should update user
should delete user
#4. ใช้ชื่อ Test ที่ไม่สื่อความหมาย
หลีกเลี่ยง
test('test1', () => {});
ควรเขียน
test(
'should reject order when quantity is zero',
() => {}
);
#36. Jest กับ CI/CD
Jest สามารถใช้งานใน CI Pipeline ได้ง่าย
ตัวอย่าง GitHub Actions
name: Unit Test
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test -- --runInBand
- name: Generate coverage
run: npm test -- --coverage --runInBand
แนวคิดคือทุกครั้งที่
Push
หรือ
Pull Request
CI จะรัน Unit Test อัตโนมัติ
หาก Test Fail จะสามารถหยุด Pipeline ก่อน Deploy ได้
#37. Workflow แนะนำ
สำหรับ Developer สามารถใช้ Workflow ดังนี้
เขียน Source Code
│
▼
เขียน Unit Test
│
▼
npm test
│
▼
แก้ Test ที่ Fail
│
▼
ตรวจ Coverage
│
▼
Git Commit
│
▼
Push GitHub
│
▼
CI รัน Jest
│
▼
Deploy
#38. คำสั่ง Jest ที่ควรรู้
| คำสั่ง | การใช้งาน |
|---|---|
npm test |
รัน Test |
npx jest |
รัน Jest |
npx jest file.test.js |
รัน Test เฉพาะไฟล์ |
npx jest -t "name" |
รัน Test ตามชื่อ |
npx jest --watch |
Watch Mode |
npx jest --coverage |
Coverage |
npx jest --runInBand |
รันทีละ Test Worker |
npx jest --clearCache |
ล้าง Cache |
#39. Checklist สำหรับ Unit Test
ก่อน Commit Code ควรตรวจสอบว่า
- [ ] Test ผ่านทั้งหมด
- [ ] Test ครอบคลุม Happy Path
- [ ] Test ครอบคลุม Error Case
- [ ] Test ครอบคลุม Boundary Case
- [ ] Test ไม่เรียก External Service โดยไม่จำเป็น
- [ ] Mock ถูก Reset ระหว่าง Test
- [ ] Test ไม่พึ่งลำดับการทำงาน
- [ ] Test มีชื่อที่อ่านเข้าใจง่าย
- [ ] Coverage อยู่ในระดับที่ทีมกำหนด
- [ ] CI สามารถรัน Test ได้อัตโนมัติ
#40. สรุป
Jest เป็น Testing Framework ที่เหมาะสำหรับการทำ Unit Test ในระบบ JavaScript และ Node.js เพราะรวมความสามารถสำคัญไว้ครบ เช่น
Test Runner
+
Expect / Matchers
+
Mock Functions
+
Module Mocking
+
Async Testing
+
Code Coverage
หลักสำคัญของ Unit Test ไม่ใช่เพียงทำให้ Coverage สูง แต่ต้องทำให้ Test สามารถตรวจสอบ พฤติกรรมที่สำคัญของระบบ ได้อย่างรวดเร็ว มีความเป็นอิสระ และให้ผลลัพธ์ที่สม่ำเสมอ
สำหรับโครงการจริงควรเริ่มจากการ Test
- Business Logic
- Validation
- Error Handling
- Utility Functions
- Service Layer
จากนั้นใช้ Mock แยก Dependency เช่น Database และ External API ออก และนำ Jest เข้าไปทำงานร่วมกับ CI/CD Pipeline เพื่อป้องกัน Regression ก่อน Merge หรือ Deploy
#เอกสารอ้างอิง
- Jest Documentation: https://jestjs.io/
- Getting Started: https://jestjs.io/docs/getting-started
- Expect / Matchers: https://jestjs.io/docs/expect
- Jest Configuration: https://jestjs.io/docs/configuration
- Jest CLI: https://jestjs.io/docs/cli
- Upgrading to Jest 30: https://jestjs.io/docs/upgrading-to-jest30