#การทดสอบ 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

  1. Business Logic
  2. Validation
  3. Error Handling
  4. Utility Functions
  5. Service Layer

จากนั้นใช้ Mock แยก Dependency เช่น Database และ External API ออก และนำ Jest เข้าไปทำงานร่วมกับ CI/CD Pipeline เพื่อป้องกัน Regression ก่อน Merge หรือ Deploy


#เอกสารอ้างอิง