# Integration Testing with Jest - คู่มือละเอียด

## สารบัญ

1. [บทนำ Integration Testing](#บทนำ)
2. [ความแตกต่างระหว่าง Unit, Integration, และ E2E Testing](#ความแตกต่าง)
3. [Setup Integration Testing with Jest](#setup)
4. [Patterns และ Best Practices](#patterns)
5. [ตัวอย่างจริง](#ตัวอย่าง)
6. [Common Scenarios](#scenarios)
7. [Tips และ Troubleshooting](#tips)

---

## บทนำ Integration Testing {#บทนำ}

### Integration Testing คืออะไร?

Integration Testing คือการทดสอบว่าส่วนประกอบต่าง ๆ ของแอปพลิเคชัน **ทำงานร่วมกันได้อย่างถูกต้อง**

#### ลำดับชั้นการทดสอบ (Test Pyramid)

```
        /\              E2E Tests
       /  \             (5-10%)
      /____\
     /      \           Integration
    /        \          Tests
   /          \         (20-30%)
  /____________\
 /              \       Unit Tests
/________________\      (70-80%)
```

### ทำไมต้อง Integration Testing?

✅ **ตรวจสอบการทำงานของระบบทั้งหมด**

- Unit tests ผ่านแต่ระบบก็ผิด?

✅ **หาปัญหา API, Database, External Services**

- เมื่อส่วนต่าง ๆ เชื่อมต่อกัน

✅ **ประหยัดต้นทุนกว่า E2E Testing**

- ไม่ต้องเปิด Browser
- ทำงานได้เร็ว

✅ **เพิ่ม Confidence ในการ Deploy**

- ลดจำนวนข้อผิดพลาดใน Production

---

## ความแตกต่างระหว่าง Unit, Integration, และ E2E Testing {#ความแตกต่าง}

### Unit Testing

```javascript
// ทดสอบ Function เดียว
describe("Calculator", () => {
  it("should add two numbers", () => {
    const result = add(2, 3);
    expect(result).toBe(5);
  });
});
```

**ทดสอบ:** Function เดียว
**DB:** Mock
**API:** Mock
**ความเร็ว:** ⚡ Very Fast

### Integration Testing

```javascript
// ✅ ทดสอบว่า API และ Database ทำงานร่วมกันได้
describe("User Registration", () => {
  it("should register user and save to DB", async () => {
    const response = await api.post("/users/register", {
      email: "test@example.com",
    });
    expect(response.status).toBe(201);

    const user = await db.collection("users").findOne({
      email: "test@example.com",
    });
    expect(user).toBeDefined();
  });
});
```

**ทดสอบ:** Multiple components ทำงานร่วมกัน
**DB:** Real (Test DB) หรือ Mock
**API:** Real Routes หรือ Mock External APIs
**ความเร็ว:** 🏃 Medium Speed

### E2E Testing (End-to-End)

```javascript
// ❌ ทดสอบจากมุมมองของผู้ใช้
describe("User Registration Flow", () => {
  it("should register user via UI", async () => {
    await page.goto("http://localhost:3000/register");
    await page.type('input[name="email"]', "test@example.com");
    await page.click('button[type="submit"]');
    await expect(page).toHaveURL(/.*\/success/);
  });
});
```

**ทดสอบ:** ทั้งระบบ UI + Backend + DB
**DB:** Real Database
**API:** Real APIs
**ความเร็ว:** 🚶 Slow (seconds)

### ตารางเปรียบเทียบ

| ลักษณะ       | Unit     | Integration         | E2E      |
| ------------ | -------- | ------------------- | -------- |
| **ช่วง**     | Function | Multiple components | Full App |
| **DB**       | Mock     | Real/Mock           | Real     |
| **APIs**     | Mock     | Real/Mock           | Real     |
| **ความเร็ว** | < 100ms  | 100ms - 1s          | 1-10s    |
| **จำนวน**    | 70-80%   | 20-30%              | 5-10%    |
| **Cost**     | Low      | Medium              | High     |
| **Debug**    | Easy     | Medium              | Hard     |

---

## Setup Integration Testing with Jest {#setup}

### 1. ติดตั้ง Dependencies

```bash
npm install --save-dev jest @types/jest
npm install --save-dev supertest  # สำหรับ API testing
npm install --save-dev @testing-library/react # React testing
npm install --save-dev mongodb-memory-server  # In-Memory DB
```

### 2. Jest Configuration (jest.config.js)

```javascript
module.exports = {
  testEnvironment: "node",
  collectCoverageFrom: ["src/**/*.js", "!src/index.js"],
  testMatch: ["**/__tests__/**/*.test.js", "**/integration/**/*.test.js"],
  setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
  testTimeout: 10000, // Integration tests อาจใช้เวลานาน
};
```

### 3. Setup File (jest.setup.js)

```javascript
// ตั้งค่าเบื้องต้น
beforeEach(() => {
  // Clear mocks ก่อนแต่ละ test
  jest.clearAllMocks();
});

afterEach(() => {
  // Cleanup หลังแต่ละ test
});

// ตั้ง timeout ให้นาน
jest.setTimeout(10000);
```

### 4. Project Structure

```
project/
├── src/
│   ├── models/
│   ├── services/
│   ├── routes/
│   └── index.js
├── __tests__/
│   ├── unit/
│   │   └── userService.test.js
│   └── integration/
│       ├── user.integration.test.js
│       └── auth.integration.test.js
├── jest.config.js
└── jest.setup.js
```

---

## Patterns และ Best Practices {#patterns}

### 1. AAA Pattern (Arrange-Act-Assert)

```javascript
describe("User Service Integration", () => {
  it("should create and retrieve user", async () => {
    // Arrange: เตรียม test data
    const userData = {
      email: "test@example.com",
      password: "password123",
      name: "Test User",
    };

    // Act: ทำการ action
    const user = await userService.createUser(userData);
    const retrievedUser = await userService.getUser(user.id);

    // Assert: ตรวจสอบผลลัพธ์
    expect(retrievedUser.email).toBe("test@example.com");
    expect(retrievedUser.id).toBe(user.id);
  });
});
```

### 2. Setup and Teardown with Database

> **อธิบาย:** Lifecycle hooks ใช้เพื่อเบิกเอา setup/cleanup ให้เหมาะสม
>
> - **beforeAll**: รันครั้งเดียวตอนเริ่มต้น (เปิดการเชื่อมต่อ DB)
> - **afterEach**: รัน**หลังแต่ละ test** (ล้างข้อมูลเพื่อไม่ให้ test ถัดไปได้รับผลกระทบ)
> - **afterAll**: รันครั้งเดียวตอนจบ (ปิดการเชื่อมต่อ DB)

```javascript
describe("User Repository Integration", () => {
  let db;
  let userCollection;

  // เรียกครั้งเดียว ตอนเริ่มต้น Suite ทั้งหมด
  // ≈ ล็อกอิน DB หนึ่งครั้ง เพื่อใช้ตลอดทั้ง test suite
  beforeAll(async () => {
    db = await MongoMemoryServer.create();
    userCollection = db.collection("users");
  });

  // เรียกหลังแต่ละ test เสร็จ
  // ≈ ล้างเซียะเพื่อให้ test ถัดไปเริ่มต้นสะอาด ๆ
  afterEach(async () => {
    await userCollection.deleteMany({}); // ลบ user ทั้งหมด
  });

  // เรียกครั้งเดียว เมื่อจบ Suite ทั้งหมด
  // ≈ ลดการเชื่อมต่อ DB ให้ปล่อยทรัพยากร
  afterAll(async () => {
    await db.stop();
  });

  it("should insert user to database", async () => {
    // Test 1: เริ่มต้นด้วยข้อมูลสะอาด (เพราะ afterEach ล้างไว้)
    const result = await userCollection.insertOne({
      email: "test@example.com",
      name: "Test",
    });

    expect(result.insertedId).toBeDefined();
  });
  // หลังทำเสร็จ → afterEach ล้างข้อมูล

  it("should insert another user", async () => {
    // Test 2: อีกเสร็จเชื่องยาง เริ่มที่สะอาดด้วยเหมือนกัน
    const result = await userCollection.insertOne({
      email: "another@example.com",
      name: "Another",
    });
    expect(result.insertedId).toBeDefined();
  });
  // หลังทำเสร็จ → afterEach ล้างข้อมูลอีก

  // เมื่อจบ Suite ทั้งหมด → afterAll ปิด DB
});
```

### 3. MongoMemoryServer - Database ในหน่วยความจำ

> **คืออะไร?**
>
> - Database จริง ๆ (MongoDB) แต่อยู่ใน RAM ของเครื่องคุณ ไม่ใช่บน server
> - ใช้สำหรับ testing เท่านั้น ได้ไว - ไม่ต้องคอยเชื่อมต่อ network
> - จะหายไปเมื่อปิดโปรแกรม (ไม่บันทึก disk)
>
> **ทำไมใช้?**
>
> - ⚡ **เร็ว**: ไม่ต้องรอ network, IO disk
> - 🔒 **ปลอดภัย**: ไม่ส่งผลต่อ production data
> - 🆕 **สะอาด**: แต่ละ test suite ได้ DB ใหม่
> - 💸 **ถูก**: ไม่ต้องจ่ายให้ server

### 4. Mocking External Services

```javascript
const axios = require("axios");
jest.mock("axios");

describe("Order Service with External Payment", () => {
  it("should create order and call payment API", async () => {
    // Mock external service
    axios.post.mockResolvedValue({
      data: { transactionId: "12345" },
      status: 200,
    });

    const order = await orderService.createOrder({
      items: [{ id: 1, quantity: 2 }],
      total: 100,
    });

    expect(axios.post).toHaveBeenCalledWith(
      "https://payment-api.com/charge",
      expect.objectContaining({ amount: 100 }),
    );
    expect(order.status).toBe("completed");
  });
});
```

### 5. Test Data Builders - Builder Pattern

> **Builder Pattern คืออะไร?**
>
> - เป็นการ**สร้างข้อมูลทีละส่วน** แบบ modular ไม่ต้องเขียนทั้งก้อน
> - ใช้ `return this` เพื่อ**chain methods** (ต่อเรียงกัน)
> - นักเรียนอัด: `.withEmail(...).withRole(...).build()`

```javascript
// ❌ ไม่ดี: ทำซ้ำ ๆ
it("test 1", () => {
  const user = {
    id: 1,
    email: "test1@example.com",
    name: "Test 1",
    role: "user",
    status: "active",
  };
});

it("test 2", () => {
  const user = {
    id: 2,
    email: "test2@example.com",
    name: "Test 2",
    role: "user",
    status: "active",
  };
});

// ✅ ดี: Builder Pattern (สร้างล่วงหน้า)
class UserBuilder {
  constructor() {
    // ค่า default ทั้งหมด
    this.user = {
      id: Math.random(), // เลขสุ่มเพื่อไม่ซ้ำ
      email: "default@example.com",
      name: "Default User",
      role: "user",
      status: "active",
    };
  }

  // method สำหรับเปลี่ยน email
  withEmail(email) {
    this.user.email = email;
    return this; // ← return this เพื่อ chain methods
  }

  // method สำหรับเปลี่ยน role
  withRole(role) {
    this.user.role = role;
    return this; // chain ต่อได้
  }

  // method สุดท้าย: เบิกข้อมูลออกมา
  build() {
    return { ...this.user }; // copy เพื่อไม่ให้เปลี่ยนหลังจากสร้าง
  }
}

// 💡 วิธีใช้: chain เรียงกัน
it("should create admin user", async () => {
  const user = new UserBuilder()
    .withEmail("admin@example.com") // ← เปลี่ยน email
    .withRole("admin") // ← เปลี่ยน role
    .build(); // ← เบิกออกมา

  const created = await userService.createUser(user);
  expect(created.role).toBe("admin");
});

it("should create moderator user", async () => {
  const user = new UserBuilder()
    .withEmail("mod@example.com")
    .withRole("moderator")
    .build();

  const created = await userService.createUser(user);
  expect(created.role).toBe("moderator");
});
```

---

## ตัวอย่างจริง {#ตัวอย่าง}

### ตัวอย่าง 1: API Endpoint Integration Testing

**src/services/userService.js**

```javascript
class UserService {
  constructor(db) {
    this.db = db;
  }

  async createUser(userData) {
    const existingUser = await this.db
      .collection("users")
      .findOne({ email: userData.email });

    if (existingUser) {
      throw new Error("User already exists");
    }

    const user = {
      ...userData,
      createdAt: new Date(),
    };

    const result = await this.db.collection("users").insertOne(user);

    return {
      id: result.insertedId,
      ...user,
    };
  }

  async getUserById(id) {
    const user = await this.db
      .collection("users")
      .findOne({ _id: new ObjectId(id) });

    return user;
  }

  async updateUser(id, updateData) {
    const result = await this.db
      .collection("users")
      .updateOne({ _id: new ObjectId(id) }, { $set: updateData });

    if (result.matchedCount === 0) {
      throw new Error("User not found");
    }

    return this.getUserById(id);
  }
}

module.exports = UserService;
```

\***\*tests**/integration/userService.integration.test.js\*\*

```javascript
const UserService = require("../../src/services/userService");
const { MongoMemoryServer } = require("mongodb-memory-server");
const { MongoClient, ObjectId } = require("mongodb");

describe("UserService Integration Tests", () => {
  let mongoServer;
  let client;
  let db;
  let userService;

  beforeAll(async () => {
    mongoServer = await MongoMemoryServer.create();
    client = new MongoClient(mongoServer.getUri());
    await client.connect();
    db = client.db("test");
    userService = new UserService(db);
  });

  afterEach(async () => {
    await db.collection("users").deleteMany({});
  });

  afterAll(async () => {
    await client.close();
    await mongoServer.stop();
  });

  describe("createUser", () => {
    it("should create user successfully", async () => {
      const userData = {
        email: "test@example.com",
        name: "Test User",
        password: "password123",
      };

      const user = await userService.createUser(userData);

      expect(user.id).toBeDefined();
      expect(user.email).toBe("test@example.com");
      expect(user.createdAt).toBeDefined();
    });

    it("should throw error if user already exists", async () => {
      const userData = {
        email: "duplicate@example.com",
        name: "Test",
      };

      await userService.createUser(userData);

      await expect(userService.createUser(userData)).rejects.toThrow(
        "User already exists",
      );
    });
  });

  describe("updateUser", () => {
    it("should update user data", async () => {
      const createdUser = await userService.createUser({
        email: "test@example.com",
        name: "Original Name",
      });

      const updated = await userService.updateUser(createdUser.id.toString(), {
        name: "Updated Name",
      });

      expect(updated.name).toBe("Updated Name");
      expect(updated.email).toBe("test@example.com");
    });

    it("should throw error if user not found", async () => {
      const fakeId = new ObjectId().toString();

      await expect(
        userService.updateUser(fakeId, { name: "New Name" }),
      ).rejects.toThrow("User not found");
    });
  });
});
```

### ตัวอย่าง 2: API Route Integration testing with Supertest

**src/routes/users.js**

```javascript
const express = require("express");
const router = express.Router();

module.exports = (userService) => {
  router.post("/register", async (req, res) => {
    try {
      const user = await userService.createUser(req.body);
      res.status(201).json(user);
    } catch (error) {
      res.status(400).json({ error: error.message });
    }
  });

  router.get("/:id", async (req, res) => {
    try {
      const user = await userService.getUserById(req.params.id);
      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }
      res.json(user);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  });

  router.put("/:id", async (req, res) => {
    try {
      const user = await userService.updateUser(req.params.id, req.body);
      res.json(user);
    } catch (error) {
      if (error.message === "User not found") {
        return res.status(404).json({ error: error.message });
      }
      res.status(500).json({ error: error.message });
    }
  });

  return router;
};
```

\***\*tests**/integration/userRoutes.integration.test.js\*\*

```javascript
const request = require("supertest");
const express = require("express");
const { MongoMemoryServer } = require("mongodb-memory-server");
const { MongoClient } = require("mongodb");
const UserService = require("../../src/services/userService");
const userRoutes = require("../../src/routes/users");

describe("User Routes Integration Tests", () => {
  let app;
  let mongoServer;
  let client;
  let db;
  let userService;

  beforeAll(async () => {
    // Setup Express app
    app = express();
    app.use(express.json());

    // Setup MongoDB
    mongoServer = await MongoMemoryServer.create();
    client = new MongoClient(mongoServer.getUri());
    await client.connect();
    db = client.db("test");
    userService = new UserService(db);

    // Setup routes
    app.use("/users", userRoutes(userService));
  });

  afterEach(async () => {
    await db.collection("users").deleteMany({});
  });

  afterAll(async () => {
    await client.close();
    await mongoServer.stop();
  });

  describe("POST /users/register", () => {
    it("should register new user", async () => {
      const response = await request(app).post("/users/register").send({
        email: "newuser@example.com",
        name: "New User",
        password: "password123",
      });

      expect(response.status).toBe(201);
      expect(response.body.email).toBe("newuser@example.com");
      expect(response.body.id).toBeDefined();
    });

    it("should return 400 if email already exists", async () => {
      const userData = {
        email: "duplicate@example.com",
        name: "User",
      };

      await request(app).post("/users/register").send(userData);

      const response = await request(app)
        .post("/users/register")
        .send(userData);

      expect(response.status).toBe(400);
      expect(response.body.error).toContain("already exists");
    });
  });

  describe("PUT /users/:id", () => {
    it("should update user", async () => {
      const registerRes = await request(app).post("/users/register").send({
        email: "test@example.com",
        name: "Original",
      });

      const userId = registerRes.body.id;

      const updateRes = await request(app)
        .put(`/users/${userId}`)
        .send({ name: "Updated Name" });

      expect(updateRes.status).toBe(200);
      expect(updateRes.body.name).toBe("Updated Name");
    });

    it("should return 404 for non-existent user", async () => {
      const fakeId = require("mongodb").ObjectId().toString();

      const response = await request(app)
        .put(`/users/${fakeId}`)
        .send({ name: "New Name" });

      expect(response.status).toBe(404);
    });
  });
});
```

### ตัวอย่าง 3: Multiple Services Integration

**src/services/authService.js**

```javascript
class AuthService {
  constructor(userService, emailService) {
    this.userService = userService;
    this.emailService = emailService;
  }

  async registerWithEmail(userData) {
    // Create user
    const user = await this.userService.createUser(userData);

    // Send welcome email
    await this.emailService.sendWelcomeEmail(user.email);

    // Return user info
    return user;
  }

  async loginUser(email, password) {
    const user = await this.userService.getUserByEmail(email);

    if (!user) {
      throw new Error("User not found");
    }

    const isValidPassword = await this.userService.validatePassword(
      password,
      user.password,
    );

    if (!isValidPassword) {
      throw new Error("Invalid password");
    }

    return user;
  }
}

module.exports = AuthService;
```

\***\*tests**/integration/authService.integration.test.js\*\*

```javascript
const AuthService = require("../../src/services/authService");
const UserService = require("../../src/services/userService");

describe("AuthService Integration Tests", () => {
  let authService;
  let userService;
  let emailService;

  beforeEach(async () => {
    // ✅ Setup Mock Database (สำหรับตัวอย่าง)
    const mongoServer = await MongoMemoryServer.create();
    const client = new MongoClient(mongoServer.getUri());
    await client.connect();
    const mockDb = client.db("test");

    // Mock email service
    emailService = {
      sendWelcomeEmail: jest.fn().mockResolvedValue(true),
    };

    // Create services
    userService = new UserService(mockDb);
    authService = new AuthService(userService, emailService);
  });

  describe("registerWithEmail", () => {
    it("should create user and send welcome email", async () => {
      const userData = {
        email: "newuser@example.com",
        password: "password123",
        name: "New User",
      };

      const user = await authService.registerWithEmail(userData);

      // Verify user was created
      expect(user.id).toBeDefined();
      expect(user.email).toBe("newuser@example.com");

      // Verify email was sent
      expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith(
        "newuser@example.com",
      );
    });

    it("should not send email if user creation fails", async () => {
      const userData = {
        email: "duplicate@example.com",
      };

      await authService.registerWithEmail(userData);

      // Try to register again
      await expect(authService.registerWithEmail(userData)).rejects.toThrow();

      // Email should only be called once
      expect(emailService.sendWelcomeEmail).toHaveBeenCalledTimes(1);
    });
  });

  describe("loginUser", () => {
    it("should login user with valid credentials", async () => {
      // ✅ Helper function สำหรับ hash password
      const hashPassword = async (password) => {
        // ในการใช้งานจริง ใช้ bcrypt
        return `hashed_${password}`;
      };

      // First register a user
      const userData = {
        email: "test@example.com",
        password: await hashPassword("password123"),
      };

      await userService.createUser(userData);

      // Login
      const user = await authService.loginUser(
        "test@example.com",
        "password123",
      );

      expect(user.email).toBe("test@example.com");
    });
  });
});
```

---

## Common Scenarios {#scenarios}

### 1. Testing Database Transactions

> **Transactions คืออะไร?**
>
> - **กลุ่มของ operations ที่ต้องสำเร็จทั้งหมดหรือล้มเหลวทั้งหมด**
> - ถ้า insert order สำเร็จแต่ update inventory ล้มเหลว → ยกเลิกทั้งคู่ (Rollback)
> - ถ้าทั้งคู่สำเร็จ → บันทึกทั้งคู่ (Commit)
> - ใช้ `session` parameter เพื่อเชื่อมต่อ operations

```javascript
describe("Order Processing with Transactions", () => {
  it("should create order and update inventory in transaction", async () => {
    const result = await db.withTransaction(async (session) => {
      try {
        // operation 1: สร้าง order
        const order = await ordersCollection.insertOne(
          {
            items: [{ productId: 1, quantity: 5 }],
            total: 500,
            status: "pending",
          },
          { session }, // ← ต้องส่ง session ทุกครั้ง
        );

        // operation 2: ลด inventory
        await productsCollection.updateOne(
          { _id: 1 },
          { $inc: { quantity: -5 } }, // ← ลบ 5 ชิ้น
          { session }, // ← ต้องส่ง session
        );

        // ถ้าทั้งคู่สำเร็จ → return ID
        return order.insertedId;
      } catch (error) {
        // ถ้าล้มเหลว → transaction ยกเลิก
        console.error("Transaction failed:", error);
        throw error;
      }
    });

    expect(result).toBeDefined();
  });
});
```

### 2. Testing Async Operations

```javascript
describe("Async Operations", () => {
  it("should handle multiple concurrent requests", async () => {
    const requests = [
      userService.createUser({ email: "user1@example.com" }),
      userService.createUser({ email: "user2@example.com" }),
      userService.createUser({ email: "user3@example.com" }),
    ];

    const users = await Promise.all(requests);

    expect(users).toHaveLength(3);
    expect(users[0].email).toBe("user1@example.com");
  });
});
```

### 3. Testing Error Handling and Recovery

```javascript
describe("Error Handling", () => {
  it("should retry failed requests", async () => {
    const mockService = {
      call: jest
        .fn()
        .mockRejectedValueOnce(new Error("Network error"))
        .mockResolvedValueOnce({ success: true }),
    };

    const result = await retryWithBackoff(() => mockService.call(), {
      maxRetries: 3,
    });

    expect(result.success).toBe(true);
    expect(mockService.call).toHaveBeenCalledTimes(2);
  });
});
```

### 4. Testing with Real vs Mock Dependencies

```javascript
describe("Hybrid Approach", () => {
  it("should test with real DB but mock external API", async () => {
    // Real database
    const user = await userService.createUser({
      email: "test@example.com",
    });

    // Mock external service
    axios.post = jest.fn().mockResolvedValue({
      data: { subscriptionId: "123" },
    });

    // Integrate
    const subscription = await subscriptionService.createSubscription(user.id);

    expect(subscription.subscriptionId).toBe("123");
  });
});
```

---

## Tips และ Troubleshooting {#tips}

### ✅ Best Practices

1. **ใช้สำเร็จรูปตั้งแต่แรก (Fixtures)**

```javascript
const DEFAULT_USER = {
  email: "test@example.com",
  name: "Test User",
  password: "password123",
};

// Reuse across tests
await userService.createUser(DEFAULT_USER);
```

2. **ทำความสะอาดหลังแต่ละ Test**

```javascript
afterEach(async () => {
  // Clear database
  await db.collection("users").deleteMany({});
  // Reset mocks
  jest.clearAllMocks();
});
```

3. **ตั้ง Timeout ให้เพียงพอ**

```javascript
jest.setTimeout(10000); // 10 seconds
```

4. **ทดสอบ Error Cases**

```javascript
it("should handle validation errors", async () => {
  await expect(userService.createUser({ email: "invalid" })).rejects.toThrow(
    "Invalid email",
  );
});
```

### 🔧 Troubleshooting

**ปัญหา: Tests ค้างอยู่ (Hanging/Stuck)**

> **"ค้างอยู่" = test รันไปเรื่อย ๆ จบไม่ได้ เพราะรอสิ่งที่ไม่มีวันสิ้นสุด**
>
> - อาจ DB ปิดไม่สำเร็จ
> - อาจ async function ไม่มี await
> - อาจ connection ยังเปิดทิ้งไว้

````javascript
// ❌ ไม่ดี: ลืม await
afterEach(() => {
  db.close(); // ← ไม่มี await! ประกาศ task แต่ไม่รอให้สำเร็จ
});

// ✅ ดี: มี await
afterEach(async () => {
  await db.close(); // ← รอให้ปิด DB สำเร็จก่อยจึงจบ test
});

// ✅ ดี: ปิด client ด้วย
afterEach(async () => {
  await client.close(); // close connection
  await db.close(); // close database
});

**ปัญหา: Database conflicts**

```javascript
// ✅ ใช้ unique test databases
const testDbName = `test_${Date.now()}`;
````

**ปัญหา: Async timing issues - Test จบได้ก่อน async task สิ้นสุด**

> **เหตุเกิด:** `setTimeout` หรือ async operation ต้องเวลา แต่ test จบเร็วเกินไป

```javascript
// ❌ ไม่ดี: test จบ ตรวจสอบยังไม่ได้
it("should work", () => {
  setTimeout(() => {
    expect(true).toBe(true);
  }, 100);
  // ← test จบตรงนี้  ตรวจสอบยังไม่ได้รัน!
});

// ✅ ดี: ใช้ callback `done`
it("should work", (done) => {
  setTimeout(() => {
    expect(true).toBe(true);
    done(); // ← บอก Jest ว่าจบแล้ว
  }, 100);
});

// ✅ ดีที่สุด: ใช้ async/await (ทันสมัย)
it("should work", async () => {
  // Promise wrapper สำหรับ setTimeout
  await new Promise((resolve) => setTimeout(resolve, 100));
  // รอถึงตรงนี้แล้วจึงทำต่อ
  expect(true).toBe(true);
});
```

### 📊 Performance Optimization

1. **ใช้ In-Memory Database**

```javascript
const mongoServer = await MongoMemoryServer.create();
// ⚡ ทำงานเร็วกว่า real database
// 💾 ข้อมูลเก็บใน RAM ไม่ใช่ disk → เร็วมาก ๆ
// 🔄 แต่ละ test suite ได้ฐาน DB ใหม่ → ไม่รบกวนกัน
```

2. **Parallel Tests**

```bash
jest --maxWorkers=4
```

3. **Skip Expensive Tests**

```javascript
it.skip("expensive operation", () => {
  // ไม่รัน test นี้
});

// หรือ
it.only("critical test", () => {
  // รัน test นี้สำหรับแก้จุดบกพร่อง
});
```

---

## สรุป

| ขั้นตอน     | โค้ด             | ผลลัพธ์              |
| ----------- | ---------------- | -------------------- |
| **Setup**   | `jest.config.js` | ✅ Framework พร้อม   |
| **Arrange** | Test data        | ✅ สภาพแวดล้อม       |
| **Act**     | เรียก function   | ✅ หลักเกณฑ์การทำงาน |
| **Assert**  | `expect()`       | ✅ ตรวจสอบผล         |

### ทรัพยากรเพิ่มเติม

- [Jest Documentation](https://jestjs.io/)
- [Supertest](https://github.com/visionmedia/supertest)
- [MongoDB Memory Server](https://github.com/mongodb-js/mongodb-memory-server)
