📅 NGÀY 53: TESTING PHILOSOPHY & STRATEGY
📍 Phase 6, Week 12, Day 53 of 75
Ngày hôm nay là khởi đầu của Phase Testing & Quality - một trong những kỹ năng quan trọng nhất để trở thành Senior Developer. Chúng ta sẽ KHÔNG viết code tests ngay (đó là ngày mai), mà thay vào đó xây dựng foundation về testing philosophy, strategy, và best practices. Bạn sẽ học cách suy nghĩ về testing như một Senior: "Test gì? Test như thế nào? Test bao nhiêu là đủ?" - những câu hỏi này quan trọng hơn việc biết syntax của testing library.
🎯 Mục tiêu học tập (5 phút)
- [ ] Hiểu Testing Pyramid và áp dụng vào React apps
- [ ] Phân biệt được test GÌ vs KHÔNG test gì
- [ ] Nắm vững AAA pattern (Arrange, Act, Assert)
- [ ] Hiểu trade-offs giữa different testing strategies
- [ ] Biết khi nào mock và khi nào KHÔNG mock
- [ ] Thiết lập testing mindset: "Test behavior, not implementation"
🤔 Kiểm tra đầu vào (5 phút)
Production Code Quality: Trong project Ngày 52, code nào bạn tự tin nhất là "chắc chắn hoạt động đúng"? Tại sao? Code nào bạn còn lo lắng nhất?
Debugging Experience: Kể về một lần bạn sửa bug, nghĩ đã fix xong, nhưng lại gây ra bug khác. Làm sao để prevent điều này?
Confidence: Nếu phải deploy project Ngày 52 lên production ngay bây giờ, bạn tự tin bao nhiêu phần trăm? Tại sao không phải 100%?
📖 PHẦN 1: GIỚI THIỆU KHÁI NIỆM (30 phút)
1.1 Vấn Đề Thực Tế
Kịch bản 1: The "Works on My Machine" Problem
// Component bạn vừa viết
function SearchBar({ onSearch }) {
const [query, setQuery] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSearch(query.trim());
};
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button>Search</button>
</form>
);
}
// Bạn test thủ công:
// ✅ Type "react" → click Search → works!
// ✅ Type " react " (with spaces) → works!
// Deploy lên production...
// 💥 User nhấn Enter thay vì click button → KHÔNG HOẠT ĐỘNG!
// 💥 User paste text có newlines → UI vỡ!
// 💥 User spam click button → duplicate searches!Vấn đề:
- Manual testing chỉ test "happy path"
- Không test edge cases
- Mỗi lần code thay đổi, phải test lại TOÀN BỘ
- Không scale khi app lớn
Kịch bản 2: The Regression Bug
// Week 1: Bạn viết function
function calculateDiscount(price, couponCode) {
if (couponCode === 'SAVE10') {
return price * 0.9;
}
return price;
}
// Week 5: PM yêu cầu thêm coupon mới
function calculateDiscount(price, couponCode) {
if (couponCode === 'SAVE10') {
return price * 0.9;
}
if (couponCode === 'SAVE20') {
return price * 0.8; // BUG: Forgot to handle case-insensitive!
}
return price;
}
// Production bug:
// User dùng "save20" (lowercase) → không được discount
// Worse: Không ai biết 'SAVE10' có còn hoạt động không!Vấn đề:
- Thay đổi code → có thể break existing functionality
- Không có safety net
- Fear of refactoring
1.2 Giải Pháp: Automated Testing
Testing là gì?
❌ TESTING KHÔNG PHẢI LÀ:
- Chạy app và click xung quanh
- "Console.log() để check"
- "Nó chạy được là ok rồi"
- Việc làm của QA team (developers không cần test)
✅ TESTING THỰC SỰ LÀ:
- Automated code kiểm tra code của bạn
- Safety net khi refactor
- Living documentation
- Confidence để shipValue của Testing:
// ❌ KHÔNG CÓ TESTS
function addToCart(item) {
cart.push(item);
updateTotal();
}
// Để biết nó hoạt động, bạn phải:
// 1. Chạy app
// 2. Click qua nhiều pages
// 3. Add item
// 4. Check cart
// 5. Verify total
// ⏱️ Mất 2-3 phút mỗi lần test
// ✅ VỚI TESTS
test('adds item to cart and updates total', () => {
const item = { id: 1, price: 100 };
addToCart(item);
expect(cart).toContain(item);
expect(getTotal()).toBe(100);
});
// ⏱️ Test chạy trong < 10ms
// 🔄 Tự động chạy mỗi khi code thay đổi
// 💰 Tiết kiệm hàng giờ đồng hồROI (Return on Investment) của Testing:
Initial Cost:
- ⏱️ 20-30% thời gian viết tests
- 📚 Learning curve
- 🛠️ Setup infrastructure
Long-term Benefits:
- 🐛 Catch bugs sớm (cheaper to fix)
- 🔄 Refactor fearlessly
- 📖 Documentation (tests show usage)
- 🚀 Ship với confidence
- ⏰ Save debugging time
- 👥 Onboard new developers faster
Break-even point: Usually after 2-3 months1.3 Mental Model: The Testing Pyramid
Cấu trúc Testing:
/\
/ \
/ E2E \ ← 10% tests (slow, expensive, brittle)
/--------\
/ \
/ Integration \ ← 20% tests (medium speed, medium cost)
/--------------\
/ \
/ Unit Tests \ ← 70% tests (fast, cheap, stable)
--------------------
BASE RULE: More unit tests, fewer E2E testsChi tiết từng loại:
1️⃣ UNIT TESTS (70%)
What: Test 1 function/component in isolation
Speed: ⚡ Very fast (< 10ms)
Cost: 💰 Cheap to write & maintain
Stability: 🎯 Very stable
When to use: Mọi business logic, utilities, pure functions
Example:
- Test calculateDiscount() function
- Test custom hook useToggle()
- Test utility formatCurrency()
2️⃣ INTEGRATION TESTS (20%)
What: Test multiple components working together
Speed: 🐢 Medium (100-500ms)
Cost: 💰💰 Medium cost
Stability: ⚠️ Moderately stable
When to use: Critical user flows
Example:
- Test form validation + submission + success message
- Test search input + filter + results list
- Test authentication flow
3️⃣ E2E TESTS (10%)
What: Test entire app like a real user
Speed: 🐌 Slow (seconds to minutes)
Cost: 💰💰💰 Expensive
Stability: 💥 Can be flaky
When to use: Critical business paths only
Example:
- Test complete checkout flow
- Test user registration → login → dashboard
- Test payment processingAnti-pattern: Inverted Pyramid
❌ BAD: Ice Cream Cone Anti-pattern
/\
/ \
/ \
/ E2E \ ← 70% tests here (DISASTER!)
/--------\
/ \
/ Integration \ ← 20% tests
/--------------\
/ Unit Tests \ ← 10% tests
------------------
Problems:
- Slow test suite (30+ minutes)
- Flaky tests (random failures)
- Expensive to maintain
- Developers skip running tests
- Low confidence despite many tests1.4 Testing Philosophy: Test Behavior, Not Implementation
❌ BAD: Testing Implementation Details
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span data-testid='count'>{count}</span>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// ❌ Implementation-focused test
test('BAD: uses useState', () => {
const { result } = renderHook(() => useState(0));
// Testing React's implementation!
});
test('BAD: calls setCount with correct value', () => {
const mockSetCount = jest.fn();
// Testing internal function calls
});
// Why bad?
// - Refactor useState → useReducer → tests break
// - Testing React, not YOUR code
// - Brittle, breaks on every refactor✅ GOOD: Testing Behavior
// ✅ Behavior-focused test
test('GOOD: increments count when button clicked', () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
const count = screen.getByText('0');
fireEvent.click(button);
expect(screen.getByText('1')).toBeInTheDocument();
});
// Why good?
// - Tests what USER sees
// - Can refactor implementation freely
// - Stable, only breaks when behavior changes
// - Documents how component should workThe Golden Rule:
🎯 TEST OUTPUTS, NOT INTERNALS
Ask yourself:
"If I refactor this code completely but keep the same behavior,
should my tests still pass?"
✅ YES → Good test (behavior-focused)
❌ NO → Bad test (implementation-focused)
Examples:
✅ "Clicking submit shows success message"
✅ "Invalid email shows error"
✅ "Total price updates when quantity changes"
❌ "Component uses useState"
❌ "Function calls API with correct params"
❌ "State updates to expected value"💻 PHẦN 2: TESTING PATTERNS & STRATEGIES (45 phút)
Demo 1: AAA Pattern (Arrange, Act, Assert) ⭐
Pattern cơ bản của mọi test:
test('descriptive test name', () => {
// 🏗️ ARRANGE: Setup the world
// - Create test data
// - Setup mocks
// - Render components
// 🎬 ACT: Do the thing
// - Click button
// - Call function
// - Trigger event
// ✅ ASSERT: Check the result
// - Verify output
// - Check side effects
// - Validate state
});Ví dụ cụ thể:
// ❌ BAD: Unclear structure
test('discount works', () => {
expect(calculateDiscount(100, 'SAVE10')).toBe(90);
const price = 200;
const result = calculateDiscount(price, 'SAVE20');
expect(result).toBe(160);
});
// ✅ GOOD: Clear AAA structure
test('applies 10% discount for SAVE10 coupon', () => {
// Arrange
const originalPrice = 100;
const couponCode = 'SAVE10';
const expectedPrice = 90;
// Act
const discountedPrice = calculateDiscount(originalPrice, couponCode);
// Assert
expect(discountedPrice).toBe(expectedPrice);
});
test('applies 20% discount for SAVE20 coupon', () => {
// Arrange
const originalPrice = 200;
const couponCode = 'SAVE20';
const expectedPrice = 160;
// Act
const discountedPrice = calculateDiscount(originalPrice, couponCode);
// Assert
expect(discountedPrice).toBe(expectedPrice);
});AAA với React Components:
// ✅ GOOD: AAA for component test
test('shows error message when email is invalid', () => {
// Arrange
render(<LoginForm />);
const emailInput = screen.getByLabelText(/email/i);
const submitButton = screen.getByRole('button', { name: /submit/i });
// Act
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
fireEvent.click(submitButton);
// Assert
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});Demo 2: Test Naming - Self-Documenting Tests ⭐⭐
❌ BAD Test Names:
test('test 1', () => {
/* ... */
});
test('it works', () => {
/* ... */
});
test('button', () => {
/* ... */
});
test('should return true', () => {
/* ... */
});✅ GOOD Test Names:
// Pattern: "should [expected behavior] when [condition]"
test('should show success message when form submitted with valid data', () => {});
test('should disable submit button when form is invalid', () => {});
test('should clear errors when user starts typing', () => {});
// Pattern: "[Action] [expected result]"
test('clicking delete button removes item from cart', () => {});
test('entering invalid email displays error message', () => {});
test('loading products shows skeleton placeholders', () => {});
// Pattern for edge cases: "[Function] [edge case] [result]"
test('calculateDiscount handles negative prices by returning 0', () => {});
test('formatCurrency handles null by returning empty string', () => {});
test('searchProducts handles empty query by returning all products', () => {});Template:
TEMPLATE FOR TEST NAMES:
Format 1: "should [expected behavior] when [condition]"
Format 2: "[action] [expected result]"
Format 3: "[feature] with [scenario] [expected outcome]"
Good test name checklist:
✅ Describes WHAT is being tested
✅ Describes EXPECTED behavior
✅ Describes WHEN/WHY it happens
✅ Readable as a sentence
✅ No need to read test code to understand
Bad test name checklist:
❌ Vague (test 1, it works)
❌ Implementation detail (uses useState)
❌ Too short (button)
❌ Too long (> 100 characters)Demo 3: What to Test vs What NOT to Test ⭐⭐⭐
🎯 WHAT TO TEST:
// ✅ 1. Business Logic
function calculateShippingCost(items, country) {
const baseRate = country === 'US' ? 5 : 10;
const itemCount = items.length;
const total = baseRate + itemCount * 2;
return itemCount > 10 ? total * 0.9 : total;
}
// TEST THIS! Critical business rules
// ✅ 2. User Interactions
function TodoApp() {
// Test: Adding todo
// Test: Completing todo
// Test: Filtering todos
}
// ✅ 3. Edge Cases
function divide(a, b) {
if (b === 0) return 0; // Edge case!
return a / b;
}
// TEST: divide by zero
// TEST: negative numbers
// TEST: very large numbers
// ✅ 4. Conditional Rendering
function UserProfile({ user }) {
if (!user) return <LoginPrompt />;
if (user.isPremium) return <PremiumProfile />;
return <BasicProfile />;
}
// TEST: Each branch
// ✅ 5. Custom Hooks
function useLocalStorage(key, initialValue) {
// Complex logic
return [value, setValue];
}
// TEST THIS! Reusable logic🚫 WHAT NOT TO TEST:
// ❌ 1. Third-party Libraries
test('useState updates state', () => {
// Don't test React's implementation!
});
test('fetch makes HTTP request', () => {
// Don't test browser APIs!
});
// ❌ 2. Implementation Details
function Component() {
const [isOpen, setIsOpen] = useState(false);
// ...
}
test('uses useState hook', () => {
// Don't care HOW it stores state
});
// ❌ 3. Trivial Code
function add(a, b) {
return a + b;
}
// Too simple, not worth testing
function HelloWorld() {
return <div>Hello World</div>;
}
// Static output, no logic
// ❌ 4. Styles/CSS
test('button has blue background', () => {
// CSS is visual, use visual regression testing
});
// ❌ 5. Console logs / Debug code
function Component() {
console.log('rendered'); // Don't test this
useEffect(() => {
console.log('effect ran'); // Don't test this
}, []);
}Decision Tree: Should I Test This?
START
|
Does it have logic?
/ \
NO YES
| |
DON'T TEST Is it YOUR code?
/ \
NO YES
| |
DON'T TEST Can users see/use it?
/ \
NO YES
| |
Is it critical? TEST IT!
/ \
NO YES
| |
MAYBE TEST TEST IT!Coverage Guidelines:
🎯 TARGET COVERAGE:
Critical Code (business logic, payments, auth):
→ 100% coverage required
Important Features (forms, dashboards):
→ 80-90% coverage
Nice-to-have Features:
→ 60-70% coverage
Utilities/Helpers:
→ 90-100% coverage (they're reused everywhere!)
⚠️ WARNING:
"100% coverage" ≠ "bug-free code"
"0% coverage" ≠ "broken code"
Quality > Quantity
Better: 50% coverage of RIGHT things
Worse: 100% coverage of WRONG things🔨 PHẦN 3: BÀI TẬP THỰC HÀNH (60 phút)
⭐ Level 1: Identify Test Scenarios (15 phút)
/**
* 🎯 Mục tiêu: Xác định WHAT to test (không viết code test)
* ⏱️ Thời gian: 15 phút
*
* Given the following code, list out:
* 1. What should be tested (5-7 scenarios)
* 2. What should NOT be tested (3-5 things)
* 3. Priority (Critical, Important, Nice-to-have)
*
* Format:
* ✅ SHOULD TEST:
* - [Scenario] - [Priority] - [Reason]
*
* ❌ SHOULD NOT TEST:
* - [Thing] - [Reason why not]
*/
// CODE TO ANALYZE:
function SearchableProductList({ products, categories }) {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [sortBy, setSortBy] = useState('name');
const filteredProducts = useMemo(() => {
let result = products;
// Filter by search term
if (searchTerm) {
result = result.filter((p) =>
p.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
}
// Filter by category
if (selectedCategory !== 'all') {
result = result.filter((p) => p.category === selectedCategory);
}
// Sort
result.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'price') return a.price - b.price;
return 0;
});
return result;
}, [products, searchTerm, selectedCategory, sortBy]);
if (products.length === 0) {
return <EmptyState message='No products available' />;
}
return (
<div>
<input
placeholder='Search products...'
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
>
<option value='all'>All Categories</option>
{categories.map((cat) => (
<option
key={cat}
value={cat}
>
{cat}
</option>
))}
</select>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
>
<option value='name'>Sort by Name</option>
<option value='price'>Sort by Price</option>
</select>
<div>
{filteredProducts.length === 0 ? (
<p>No products match your filters</p>
) : (
filteredProducts.map((product) => (
<ProductCard
key={product.id}
product={product}
/>
))
)}
</div>
</div>
);
}💡 Solution - Test Scenarios
✅ SHOULD TEST:
CRITICAL (Must test):
1. Search filtering - Critical - Core feature, users depend on it
- "filters products by search term (case-insensitive)"
- "shows all products when search is empty"
2. Category filtering - Critical - Core feature
- "filters products by selected category"
- "shows all products when 'all' category selected"
3. Empty states - Critical - User experience
- "shows empty state when no products provided"
- "shows 'no match' message when filters return empty"
4. Combined filters - Important - Common user behavior
- "applies both search and category filters together"
IMPORTANT (Should test): 5. Sorting - Important - Expected feature
- "sorts products by name alphabetically"
- "sorts products by price numerically"
6. Edge cases - Important - Prevent bugs
- "handles search with special characters"
- "handles null/undefined products array"
NICE-TO-HAVE (Can test): 7. Performance - Nice-to-have - useMemo working
- "doesn't refilter when unrelated state changes"
❌ SHOULD NOT TEST:
1. useMemo internals - Testing React's implementation
2. useState updates - Testing React's implementation
3. Product card rendering - Should be tested in ProductCard component
4. CSS/Styling - Visual testing, not unit tests
5. localeCompare behavior - Testing browser API
6. Array.sort stability - Testing JavaScript
📝 NOTES:
- Focus on USER BEHAVIOR, not implementation
- Test OUTPUTS (what user sees), not INTERNALS (how it works)
- Edge cases are important but lower priority than happy path⭐⭐ Level 2: Write Test Descriptions (25 phút)
/**
* 🎯 Mục tiêu: Viết test descriptions theo AAA pattern
* ⏱️ Thời gian: 25 phút
*
* For each scenario below, write:
* 1. Test name (descriptive, follows format)
* 2. AAA structure (ts-code, không cần real syntax)
*
* Format:
* test('[descriptive name]', () => {
* // Arrange: [what you setup]
* // Act: [what action you perform]
* // Assert: [what you expect]
* });
*
* 🚫 KHÔNG viết actual test code (chưa học Jest/RTL)
* ✅ Chỉ viết ts-code và structure
*/
// SCENARIOS TO TEST:
// 1. Login Form Component
function LoginForm({ onSubmit }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = {};
if (!email) newErrors.email = 'Email is required';
if (!email.includes('@')) newErrors.email = 'Email is invalid';
if (!password) newErrors.password = 'Password is required';
if (password.length < 8)
newErrors.password = 'Password must be 8+ characters';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
onSubmit({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type='email'
placeholder='Email'
value={email}
onChange={(e) => setEmail(e.target.value)}
aria-label='Email'
/>
{errors.email && <span role='alert'>{errors.email}</span>}
<input
type='password'
placeholder='Password'
value={password}
onChange={(e) => setPassword(e.target.value)}
aria-label='Password'
/>
{errors.password && <span role='alert'>{errors.password}</span>}
<button type='submit'>Login</button>
</form>
);
}
// TODO: Write 5-7 test descriptions covering:
// - Happy path (valid submission)
// - Email validation (empty, invalid format)
// - Password validation (empty, too short)
// - Error clearing (errors disappear when user types)💡 Solution - Test Descriptions
/**
* Test Suite for LoginForm
*
* Coverage:
* - Form submission (happy path)
* - Email validation (required, format)
* - Password validation (required, length)
* - Error handling & clearing
*/
// ✅ TEST 1: Happy Path
test('calls onSubmit with email and password when form is valid', () => {
// Arrange
// - Render LoginForm component
// - Create mock onSubmit function
// - Get email input, password input, submit button
// Act
// - Type valid email into email input
// - Type valid password into password input
// - Click submit button
// Assert
// - onSubmit was called once
// - onSubmit was called with { email, password }
// - No error messages visible
});
// ✅ TEST 2: Email Required
test('shows error message when email is empty', () => {
// Arrange
// - Render LoginForm
// - Get password input and submit button
// Act
// - Type valid password
// - Click submit button (email still empty)
// Assert
// - Error message "Email is required" is visible
// - onSubmit was NOT called
});
// ✅ TEST 3: Email Format
test('shows error message when email is invalid format', () => {
// Arrange
// - Render LoginForm
// - Get email input and submit button
// Act
// - Type "notanemail" (no @)
// - Click submit button
// Assert
// - Error message "Email is invalid" is visible
// - onSubmit was NOT called
});
// ✅ TEST 4: Password Required
test('shows error message when password is empty', () => {
// Arrange
// - Render LoginForm
// - Get email input and submit button
// Act
// - Type valid email
// - Click submit (password empty)
// Assert
// - Error message "Password is required" is visible
// - onSubmit was NOT called
});
// ✅ TEST 5: Password Length
test('shows error when password is less than 8 characters', () => {
// Arrange
// - Render LoginForm
// - Get password input and submit button
// Act
// - Type valid email
// - Type "pass" (only 4 characters)
// - Click submit
// Assert
// - Error message "Password must be 8+ characters" is visible
// - onSubmit was NOT called
});
// ✅ TEST 6: Multiple Errors
test('shows all validation errors when both fields are invalid', () => {
// Arrange
// - Render LoginForm
// - Get submit button
// Act
// - Leave email empty
// - Leave password empty
// - Click submit
// Assert
// - Email error is visible
// - Password error is visible
// - Both errors shown at the same time
});
// ✅ TEST 7: Error Clearing
test('clears error message when user starts typing', () => {
// Arrange
// - Render LoginForm
// - Submit form with empty email (trigger error)
// - Verify error is visible
// Act
// - Type into email input
// Assert
// - Error message is no longer visible
// OR
// - Error message updates to reflect new validation
});
// 📝 NOTES:
// - Test names describe BEHAVIOR, not implementation
// - AAA structure is clear in each test
// - Focus on what USER sees/does
// - Edge cases covered (empty, invalid, multiple errors)⭐⭐⭐ Level 3: Design Test Strategy (40 phút)
/**
* 🎯 Mục tiêu: Thiết kế comprehensive test strategy
* ⏱️ Thời gian: 40 phút
*
* Given a Shopping Cart feature, design a complete test strategy:
*
* 1. List all components/functions in the feature
* 2. Categorize tests (Unit, Integration, E2E)
* 3. Decide what to mock vs real
* 4. Estimate coverage targets
* 5. Identify critical paths
*
* Deliverable: Test Strategy Document (markdown format)
*/
// FEATURE: Shopping Cart
//
// Components:
// - CartPage (main container)
// - CartItem (individual item row)
// - CartSummary (subtotal, tax, total)
// - CheckoutButton
//
// Functions:
// - calculateSubtotal(items)
// - calculateTax(subtotal, taxRate)
// - calculateTotal(subtotal, tax, shipping)
// - validateCheckout(items, user)
//
// Context:
// - CartContext (state management)
// - useCart() hook
//
// API:
// - updateQuantity(itemId, quantity)
// - removeItem(itemId)
// - checkout(items, paymentMethod)
// TODO: Create test strategy document covering:
// 1. Testing pyramid distribution
// 2. Unit tests breakdown
// 3. Integration tests breakdown
// 4. E2E tests (if any)
// 5. Mocking strategy
// 6. Coverage targets per component
// 7. Critical paths to prioritize💡 Solution - Test Strategy Document
# 🛒 Shopping Cart - Test Strategy
## 1. Testing Pyramid Distribution
```
/\
/ 2 \ E2E: 2 tests (10%)
/-----\ - Complete checkout flow
/ 4 \ Integration: 4 tests (20%)
/---------\ - Cart interactions
/ 14 \ Unit: 14 tests (70%)
/-------------\ - Functions, components, hooks
```
---Total: ~20 testsEstimated time: Unit (2h), Integration (1.5h), E2E (1h)
2. Unit Tests (14 tests)
2.1 Pure Functions (6 tests) - 100% coverage required
calculateSubtotal(items)
- ✅ returns 0 for empty cart
- ✅ calculates correct sum for single item
- ✅ calculates correct sum for multiple items
- ✅ handles quantity correctly (price × quantity)
calculateTax(subtotal, taxRate)
- ✅ calculates tax correctly (subtotal × taxRate)
- ✅ returns 0 when taxRate is 0
calculateTotal(subtotal, tax, shipping)
- ✅ sums all components correctly
- ✅ handles edge cases (0 values)
Priority: 🔴 CRITICAL Rationale: Business logic, affects pricing
2.2 useCart Hook (4 tests) - 90% coverage
Custom Hook Testing:
- ✅ initializes with empty cart
- ✅ addItem adds item to cart
- ✅ updateQuantity updates item quantity
- ✅ removeItem removes item from cart
Priority: 🔴 CRITICAL Rationale: Core state management
2.3 Individual Components (4 tests) - 80% coverage
CartItem Component:
- ✅ displays item name, price, quantity
- ✅ calls onQuantityChange when quantity updated
- ✅ calls onRemove when delete clicked
CartSummary Component:
- ✅ displays subtotal, tax, total correctly
Priority: 🟡 IMPORTANT Rationale: UI components, less critical than logic
3. Integration Tests (4 tests)
3.1 Component Integration (3 tests)
CartPage + CartContext:
- ✅ adding item shows in cart list
- ✅ updating quantity recalculates totals
- ✅ removing item updates cart and totals
Priority: 🔴 CRITICAL Rationale: Core user flows
3.2 API Integration (1 test)
Cart + API:
- ✅ checkout sends correct data to API
Priority: 🟡 IMPORTANT Rationale: Important but mocked in integration tests
4. E2E Tests (2 tests)
4.1 Critical Path (2 tests)
Happy Path:
- ✅ User adds items → updates quantities → checks out successfully
Error Path:
- ✅ User attempts checkout with invalid payment → sees error → retries
Priority: 🔴 CRITICAL Rationale: Business-critical flows
5. Mocking Strategy
What to Mock:
// ✅ MOCK THESE:
- API calls (fetch/axios)
→ Use MSW or jest.mock()
→ Reason: Don't hit real backend
- localStorage
→ Mock with jest
→ Reason: Not available in test environment
- Date.now() / Math.random()
→ Mock for deterministic tests
→ Reason: Tests should be predictable
- Payment gateway
→ Mock completely
→ Reason: Don't charge real cards!What NOT to Mock:
// ❌ DON'T MOCK THESE:
- React hooks (useState, useEffect)
→ Test real behavior
- Child components (in integration tests)
→ Test real interactions
- Pure functions (calculateTotal, etc.)
→ Test real logic
- Context providers
→ Use real providers in tests6. Coverage Targets
| Component/Function | Target | Priority | Rationale |
|---|---|---|---|
| Pure functions | 100% | 🔴 Critical | Business logic |
| useCart hook | 90% | 🔴 Critical | Core state |
| CartPage | 80% | 🟡 Important | Main UI |
| CartItem | 70% | 🟢 Nice | Simple component |
| CartSummary | 80% | 🟡 Important | Shows money |
| validateCheckout | 100% | 🔴 Critical | Prevents bad orders |
Overall Target: 85% coverage
7. Critical Paths (Must Test First)
Priority 1 (Ship-blockers):
- ✅ Adding items to cart
- ✅ Quantity updates recalculate correctly
- ✅ Checkout with valid data succeeds
- ✅ Total calculation accuracy
Priority 2 (Important):
- ✅ Removing items
- ✅ Empty cart state
- ✅ Checkout validation errors
Priority 3 (Nice-to-have):
- ✅ Cart persistence (localStorage)
- ✅ Item quantity limits
- ✅ Discount codes
8. Test Organization
tests/
├── unit/
│ ├── calculations.test.js # Pure functions
│ ├── useCart.test.js # Custom hook
│ ├── CartItem.test.js # Component
│ └── CartSummary.test.js # Component
│
├── integration/
│ ├── CartPage.test.js # Full page with context
│ └── checkout.test.js # API integration
│
└── e2e/
└── shopping-flow.spec.js # Complete user journey9. Testing Timeline
Week 1:
- Day 1-2: Setup + Pure function tests (6 tests)
- Day 3: useCart hook tests (4 tests)
- Day 4: Component unit tests (4 tests)
Week 2:
- Day 1-2: Integration tests (4 tests)
- Day 3: E2E tests (2 tests)
- Day 4: Fix flaky tests, increase coverage
Total: 8 working days
10. Success Metrics
Before Ship:
- [ ] All critical paths covered
- [ ] 85%+ overall coverage
- [ ] 100% coverage on money calculations
- [ ] 0 flaky tests
- [ ] All tests run in < 30 seconds
- [ ] E2E tests pass 95%+ of the time
Maintenance:
- [ ] New features include tests
- [ ] Coverage doesn't drop below 80%
- [ ] Test failures investigated within 1 hour
- [ ] Flaky tests fixed within 1 day
⭐⭐⭐⭐ Level 4: Critique Test Code (60 phút)
/**
* 🎯 Mục tiêu: Phân tích và cải thiện bad tests
* ⏱️ Thời gian: 60 phút
*
* Below are REAL BAD TESTS from production codebases.
*
* For each test:
* 1. Identify what's wrong (list all issues)
* 2. Explain WHY it's problematic
* 3. Rewrite as a GOOD test
* 4. Explain what makes the rewrite better
*
* 🚫 KHÔNG viết actual test code
* ✅ Viết ts-code structure với giải thích
*/
// BAD TEST 1: Implementation Details
test('counter component', () => {
const wrapper = shallow(<Counter />);
const instance = wrapper.instance();
expect(instance.state.count).toBe(0);
instance.increment();
expect(instance.state.count).toBe(1);
expect(wrapper.find('span').text()).toBe('1');
});
// BAD TEST 2: Testing Too Much
test('user registration flow', () => {
// 50 lines of test code...
// Tests: form validation, API call, redirect, toast message,
// analytics event, localStorage update, email confirmation
});
// BAD TEST 3: Unclear Test
test('test 1', () => {
const result = doSomething();
expect(result).toBeTruthy();
});
// BAD TEST 4: Brittle Test
test('form has red border when invalid', () => {
const { container } = render(<Form />);
const form = container.querySelector('form');
expect(form.style.borderColor).toBe('rgb(255, 0, 0)');
expect(form.style.borderWidth).toBe('2px');
expect(form.className).toContain('error-border');
});
// BAD TEST 5: Not Testing Behavior
test('clicking button calls handleClick', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick} />);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalled();
});
// TODO: Analyze each test and provide critique + rewrite💡 Solution - Test Critiques
# 🔍 Test Code Critique & Improvements
---
## BAD TEST 1: Implementation Details
### ❌ What's Wrong:
1. **Testing internal state directly**
- `instance.state.count` - testing React internals
2. **Calling instance methods**
- `instance.increment()` - bypassing UI
3. **Using shallow rendering**
- Doesn't test real user experience
4. **Testing HOW instead of WHAT**
- Testing implementation, not behavior
### 💥 Why Problematic:
- Breaks if you refactor useState → useReducer
- Breaks if you rename state variable
- Doesn't test actual user interaction
- False confidence (passes but app might be broken)
### ✅ GOOD Rewrite:
```ts
test('increments count when increment button clicked', () => {
// Arrange
// - Render <Counter /> component
// - Find increment button by role/text
// - Find count display element
// Act
// - Click increment button
// Assert
// - Count display shows "1"
// - (Optional) Verify button is still enabled
});
```🌟 What Makes It Better:
- Tests USER behavior (click button)
- Tests OUTPUT (what user sees)
- Works regardless of internal implementation
- Breaks only when actual behavior changes
BAD TEST 2: Testing Too Much
❌ What's Wrong:
One test doing too much
- Violates Single Responsibility Principle
50 lines in one test
- Hard to read, hard to maintain
Multiple reasons to fail
- If any part fails, whole test fails
- Hard to debug which part broke
Mixing unit + integration + E2E
- Should be separate tests
💥 Why Problematic:
- One feature breaks → entire test fails
- Can't tell WHAT failed from test name
- Slow to run
- Nightmare to maintain
✅ GOOD Rewrite:
// Split into focused tests:
test('shows validation error when email is invalid', () => {
// Just test form validation
});
test('calls registration API with correct data', () => {
// Just test API call
});
test('redirects to dashboard after successful registration', () => {
// Just test redirect
});
test('shows success toast after registration', () => {
// Just test toast
});
// Integration test (separate file):
test('complete registration flow works end-to-end', () => {
// Test the whole flow
});🌟 What Makes It Better:
- Each test has one job
- Clear what failed when test breaks
- Faster to run (parallel)
- Easier to maintain
BAD TEST 3: Unclear Test
❌ What's Wrong:
Vague test name
- "test 1" - meaningless
Vague assertion
toBeTruthy()- what is expected?
No context
- What is
doSomething()? - What should result be?
- What is
Not self-documenting
- Have to read code to understand
💥 Why Problematic:
- Future developer (or you in 3 months) won't know what this tests
- Fails? No idea what's broken
- Provides zero documentation value
✅ GOOD Rewrite:
test('formatCurrency returns formatted string with $ and 2 decimals', () => {
// Arrange
// - Input: number 1234.567
// Act
// - result = formatCurrency(1234.567)
// Assert
// - result equals "$1,234.57"
// - (Specific, clear expectation)
});
test('formatCurrency handles negative numbers with minus sign', () => {
// Arrange
// - Input: -1234.567
// Act
// - result = formatCurrency(-1234.567)
// Assert
// - result equals "-$1,234.57"
});🌟 What Makes It Better:
- Test name explains WHAT is being tested
- Test name explains EXPECTED behavior
- Specific assertions (
toBe()nottoBeTruthy()) - Self-documenting
BAD TEST 4: Brittle Test (Testing Styles)
❌ What's Wrong:
- Testing CSS styles
borderColor,borderWidth- implementation details
- Exact color values
rgb(255, 0, 0)- too specific
- Testing className
- Implementation detail
- Breaks on any style change
- Designer changes red to orange → test breaks
💥 Why Problematic:
- CSS changes break tests
- Not testing actual validation logic
- Testing presentation, not behavior
- False negatives (test fails but feature works)
✅ GOOD Rewrite:
test('shows validation error message when form is invalid', () => {
// Arrange
// - Render form
// - Leave required field empty
// Act
// - Submit form
// Assert
// - Error message "This field is required" is visible
// - Form was NOT submitted (check onSubmit not called)
});
test('form is marked invalid when required field empty', () => {
// Arrange
// - Render form
// Act
// - Submit with empty required field
// Assert
// - Form has aria-invalid="true"
// - Error message has role="alert"
// - (Test accessibility, not CSS)
});🌟 What Makes It Better:
- Tests behavior (error shown), not styles
- Tests accessibility (a11y attributes)
- Works regardless of CSS changes
- Tests what matters to users
Note: For visual regression (colors, layouts), use dedicated tools:
- Percy, Chromatic (visual diff)
- Screenshot tests
- NOT unit tests!
BAD TEST 5: Not Testing Behavior
❌ What's Wrong:
- Testing that function was called
- Not testing OUTCOME
- Mocking the thing you should test
handleClickis mocked
- Not verifying what happens
- Function called... then what?
💥 Why Problematic:
- Test passes even if button doesn't work
- Not testing actual functionality
- Testing wiring, not behavior
- False positive (test passes, feature broken)
✅ GOOD Rewrite:
// Option 1: Test the actual outcome
test('clicking add to cart button adds item to cart', () => {
// Arrange
// - Render ProductCard with item
// - Cart is initially empty
// Act
// - Click "Add to Cart" button
// Assert
// - Cart now contains 1 item
// - Cart shows item name and price
// - (Test the RESULT, not the function call)
});
// Option 2: If testing integration with parent
test('clicking button triggers parent callback with item data', () => {
// Arrange
// - Create mock onAddToCart
// - Render Button with specific item data
// Act
// - Click button
// Assert
// - onAddToCart called with correct item object
// - onAddToCart called exactly once
// - (Verify DATA passed, not just "called")
});🌟 What Makes It Better:
- Tests outcome (item in cart) not implementation
- Verifies correct data passed
- Tests integration, not just wiring
- Real user value tested
📝 Summary of Test Smells
| Smell | Example | Fix |
|---|---|---|
| Implementation details | Testing state.count | Test what user sees |
| Too much in one test | 50-line test | Split into focused tests |
| Unclear names | "test 1" | Descriptive names |
| Brittle (CSS) | Testing borderColor | Test behavior/a11y |
| Mock everything | Mock function called | Test real outcome |
| No AAA structure | Random assertions | Clear Arrange/Act/Assert |
| Testing libraries | Testing React/fetch | Test YOUR code |
🎯 Golden Rules
- Test behavior, not implementation
- One assertion per test (or related assertions)
- Descriptive test names
- AAA structure always
- Test outputs, not internals
⭐⭐⭐⭐⭐ Level 5: Design Complete Test Suite (90 phút)
/**
* 🎯 Mục tiêu: Thiết kế full test suite cho real-world feature
* ⏱️ Thời gian: 90 phút
*
* Design comprehensive test suite for User Authentication feature
*
* Deliverables:
* 1. Test Plan Document (markdown)
* 2. Test file structure
* 3. All test descriptions (AAA ts-code)
* 4. Mocking strategy
* 5. CI/CD integration plan
*
* Requirements:
* - Testing pyramid compliant
* - Critical paths covered
* - Edge cases identified
* - Performance considerations
* - A11y considerations
*/
// FEATURE SPEC: User Authentication
//
// Features:
// 1. Login (email/password)
// 2. Logout
// 3. Password Reset (send email)
// 4. Registration (with email verification)
// 5. Social Login (Google, GitHub)
// 6. Session Management (JWT tokens)
// 7. Protected Routes
//
// Components:
// - LoginForm
// - RegisterForm
// - ForgotPasswordForm
// - ProtectedRoute
// - AuthProvider (Context)
// - useAuth() hook
//
// API Endpoints:
// - POST /api/auth/login
// - POST /api/auth/register
// - POST /api/auth/logout
// - POST /api/auth/forgot-password
// - POST /api/auth/reset-password
// - GET /api/auth/me (verify session)
//
// Security:
// - CSRF protection
// - Rate limiting
// - Input sanitization
// - Password strength validation
// TODO: Create complete test plan covering all aspects💡 Solution - Complete Test Suite Plan
# 🔐 User Authentication - Complete Test Suite
## Executive Summary
**Scope:** Full authentication system
**Est. Tests:** 45 tests (32 unit, 10 integration, 3 E2E)
**Est. Time:** 3 weeks (1 developer)
**Coverage Target:** 90% overall, 100% security-critical code
---
## 1. Testing Pyramid
```
/\
/3 \ E2E: 3 tests (7%)
/----\
/ 10 \ Integration: 10 tests (22%)
/--------\
/ 32 \ Unit: 32 tests (71%)
/------------\
```
**Rationale:**
- Auth is critical → higher than average coverage
- Security bugs expensive → invest in testing
- Complex state → need integration tests
---
## 2. Test File Structure
```
tests/
├── unit/
│ ├── components/
│ │ ├── LoginForm.test.js (7 tests)
│ │ ├── RegisterForm.test.js (8 tests)
│ │ ├── ForgotPasswordForm.test.js (4 tests)
│ │ └── ProtectedRoute.test.js (3 tests)
│ │
│ ├── hooks/
│ │ └── useAuth.test.js (6 tests)
│ │
│ └── utils/
│ └── validation.test.js (4 tests)
│
├── integration/
│ ├── login-flow.test.js (3 tests)
│ ├── registration-flow.test.js (3 tests)
│ ├── password-reset-flow.test.js (2 tests)
│ └── protected-routes.test.js (2 tests)
│
├── e2e/
│ └── auth-critical-paths.spec.js (3 tests)
│
└── setup/
├── test-utils.js
├── mocks/
│ ├── authApi.js
│ └── authContext.js
└── fixtures/
└── users.js
```
---
## 3. Unit Tests Detailed (32 tests)
### 3.1 LoginForm Component (7 tests)
```jsx
// ✅ TEST 1: Happy Path
test('submits form with email and password when valid', () => {
// Arrange
// - Render LoginForm with mock onSubmit
// - Get email, password inputs and submit button
// Act
// - Type valid email: "user@example.com"
// - Type valid password: "SecurePass123!"
// - Click submit button
// Assert
// - onSubmit called with { email, password }
// - No error messages visible
});
// ✅ TEST 2: Email Validation - Empty
test('shows error when email is empty', () => {
// Arrange: Render form
// Act: Submit with empty email
// Assert: "Email is required" visible
});
// ✅ TEST 3: Email Validation - Invalid Format
test('shows error when email is invalid format', () => {
// Arrange: Render form
// Act: Type "notanemail", submit
// Assert: "Invalid email address" visible
});
// ✅ TEST 4: Password Validation - Empty
test('shows error when password is empty', () => {
// Arrange: Render form
// Act: Submit with empty password
// Assert: "Password is required" visible
});
// ✅ TEST 5: Error Clearing
test('clears error when user corrects input', () => {
// Arrange: Submit invalid form (has error)
// Act: Type valid email
// Assert: Error message removed
});
// ✅ TEST 6: Loading State
test('disables submit button and shows loading when submitting', () => {
// Arrange: Render with slow onSubmit
// Act: Click submit
// Assert: Button disabled, shows "Loading..."
});
// ✅ TEST 7: Server Error Display
test('shows server error message when login fails', () => {
// Arrange: Render with onSubmit that rejects
// Act: Submit valid form
// Assert: Server error message displayed
});
```3.2 RegisterForm Component (8 tests)
// ✅ TEST 1: Happy Path
test('submits registration with all required fields', () => {
// Arrange: Render form
// Act: Fill email, password, confirm password, accept terms
// Assert: onSubmit called with correct data
});
// ✅ TEST 2: Password Strength - Too Short
test('shows error when password is less than 8 characters', () => {
// Arrange: Render form
// Act: Type "Pass1!"
// Assert: "Password must be at least 8 characters" shown
});
// ✅ TEST 3: Password Strength - No Uppercase
test('shows error when password has no uppercase letter', () => {
// Arrange: Render form
// Act: Type "password123!"
// Assert: "Password must contain uppercase letter" shown
});
// ✅ TEST 4: Password Strength - No Number
test('shows error when password has no number', () => {
// Arrange: Render form
// Act: Type "Password!"
// Assert: "Password must contain number" shown
});
// ✅ TEST 5: Password Confirmation Mismatch
test('shows error when passwords do not match', () => {
// Arrange: Render form
// Act: Type password "Pass123!", confirm "Pass456!"
// Assert: "Passwords do not match" shown
});
// ✅ TEST 6: Terms Acceptance Required
test('shows error when terms not accepted', () => {
// Arrange: Render form
// Act: Fill form but don't check terms checkbox
// Assert: "You must accept terms" shown
});
// ✅ TEST 7: Email Already Exists
test('shows error when email is already registered', () => {
// Arrange: Render with onSubmit that rejects with "Email exists"
// Act: Submit
// Assert: "Email already registered" shown
});
// ✅ TEST 8: Password Visibility Toggle
test('toggles password visibility when eye icon clicked', () => {
// Arrange: Render form
// Act: Click eye icon
// Assert: input type changes from "password" to "text"
});3.3 useAuth Hook (6 tests)
// ✅ TEST 1: Initial State
test('initializes with null user and loading false', () => {
// Arrange: Render hook
// Assert:
// - user is null
// - loading is false
// - isAuthenticated is false
});
// ✅ TEST 2: Login Updates State
test('sets user and isAuthenticated after successful login', () => {
// Arrange: Render hook
// Act: Call login() with credentials
// Assert:
// - user object set
// - isAuthenticated is true
// - loading is false
});
// ✅ TEST 3: Logout Clears State
test('clears user state when logout called', () => {
// Arrange: Login first (user set)
// Act: Call logout()
// Assert:
// - user is null
// - isAuthenticated is false
// - token removed from storage
});
// ✅ TEST 4: Token Persistence
test('loads user from token on mount if valid token exists', () => {
// Arrange: Set valid token in localStorage
// Act: Render hook
// Assert: User loaded from token
});
// ✅ TEST 5: Expired Token Handling
test('clears state when token is expired', () => {
// Arrange: Set expired token in localStorage
// Act: Render hook
// Assert: User is null, token removed
});
// ✅ TEST 6: Error Handling
test('sets error state when login fails', () => {
// Arrange: Render hook
// Act: Call login() with invalid credentials
// Assert: error state set with message
});3.4 Validation Utils (4 tests)
// ✅ TEST 1: Email Validation
test('validateEmail returns true for valid emails', () => {
// Test: user@example.com → true
// Test: user+tag@example.co.uk → true
});
test('validateEmail returns false for invalid emails', () => {
// Test: notanemail → false
// Test: @example.com → false
// Test: user@.com → false
});
// ✅ TEST 2: Password Strength
test('validatePasswordStrength checks all criteria', () => {
// Test: "Pass123!" → { valid: true, errors: [] }
// Test: "pass" → { valid: false, errors: [...] }
});
// ✅ TEST 3: Sanitize Input
test('sanitizeInput removes malicious characters', () => {
// Test: "<script>alert('xss')</script>" → cleaned
});4. Integration Tests (10 tests)
4.1 Login Flow (3 tests)
// ✅ TEST 1: Complete Login Success
test('user can login and see dashboard', () => {
// Arrange
// - Render entire app with AuthProvider
// - Mock API to return success
// Act
// - Navigate to login page
// - Fill email and password
// - Click submit
// Assert
// - API called with credentials
// - User redirected to dashboard
// - User name displayed in header
// - Token stored in localStorage
});
// ✅ TEST 2: Login Failure Shows Error
test('displays error message when credentials are invalid', () => {
// Arrange: Mock API to return 401
// Act: Submit login form
// Assert: Error message shown, no redirect
});
// ✅ TEST 3: Remember Me Functionality
test('persists session when remember me is checked', () => {
// Arrange: Render login
// Act: Check "Remember me", login
// Assert: Token persists across page refresh
});4.2 Registration Flow (3 tests)
// ✅ TEST 1: Complete Registration
test('user can register and receive verification email', () => {
// Arrange: Mock API
// Act: Complete registration form
// Assert:
// - API called with user data
// - Success message shown
// - "Check your email" message
});
// ✅ TEST 2: Duplicate Email Handling
test('shows error when email already exists', () => {
// Arrange: Mock API to return conflict
// Act: Submit registration
// Assert: "Email already registered" shown
});
// ✅ TEST 3: Email Verification
test('verifies email with valid token', () => {
// Arrange: Mock API with verify endpoint
// Act: Visit /verify?token=xxx
// Assert: Account activated, can login
});4.3 Password Reset (2 tests)
// ✅ TEST 1: Request Reset Email
test('sends password reset email for valid email', () => {
// Arrange: Render forgot password form
// Act: Enter email, submit
// Assert: API called, success message shown
});
// ✅ TEST 2: Reset Password with Token
test('resets password with valid token', () => {
// Arrange: Visit /reset-password?token=xxx
// Act: Enter new password, submit
// Assert: Password updated, redirected to login
});4.4 Protected Routes (2 tests)
// ✅ TEST 1: Redirect When Not Authenticated
test('redirects to login when accessing protected route', () => {
// Arrange: User not logged in
// Act: Navigate to /dashboard
// Assert: Redirected to /login
});
// ✅ TEST 2: Allow Access When Authenticated
test('allows access to protected route when logged in', () => {
// Arrange: User logged in
// Act: Navigate to /dashboard
// Assert: Dashboard content visible
});5. E2E Tests (3 tests)
// ✅ TEST 1: Critical Path - Complete User Journey
test('new user can register, verify email, and login', () => {
// 1. Visit registration page
// 2. Fill form with valid data
// 3. Submit registration
// 4. Check email (mock inbox)
// 5. Click verification link
// 6. See success message
// 7. Login with credentials
// 8. See dashboard
});
// ✅ TEST 2: Session Persistence
test('user stays logged in after page refresh', () => {
// 1. Login
// 2. Navigate to different pages
// 3. Refresh page
// 4. Still logged in
// 5. Logout
// 6. Session cleared
});
// ✅ TEST 3: Password Reset Flow
test('user can reset forgotten password', () => {
// 1. Click "Forgot password"
// 2. Enter email
// 3. Check email (mock)
// 4. Click reset link
// 5. Enter new password
// 6. Login with new password
// 7. Success
});6. Mocking Strategy
✅ Mock These:
// API Calls
- All auth endpoints (login, register, etc.)
- Use MSW (Mock Service Worker)
- Realistic delays (100-300ms)
// Third-party Services
- Social auth (Google, GitHub)
- Email service
- Analytics
// Browser APIs
- localStorage
- sessionStorage
- window.location
// Time-based
- Date.now() (for token expiry)
- setTimeout/setInterval❌ Don't Mock These:
// React
- useState, useEffect, useContext
- Test real behavior
// Your Code
- Validation functions
- Custom hooks
- Components
// Context Providers
- AuthProvider
- Use real provider in testsMock Examples:
// MSW Setup
handlers = [
rest.post('/api/auth/login', (req, res, ctx) => {
const { email, password } = req.body;
// Simulate validation
if (email === 'user@example.com' && password === 'Pass123!') {
return res(
ctx.delay(100),
ctx.json({
user: { id: 1, email, name: 'Test User' },
token: 'mock-jwt-token',
}),
);
}
return res(
ctx.delay(100),
ctx.status(401),
ctx.json({ error: 'Invalid credentials' }),
);
}),
];7. Coverage Targets
| Component/Feature | Target | Priority |
|---|---|---|
| Login logic | 100% | 🔴 Critical |
| Registration | 100% | 🔴 Critical |
| Password validation | 100% | 🔴 Critical |
| useAuth hook | 95% | 🔴 Critical |
| ProtectedRoute | 100% | 🔴 Critical |
| Forms (UI) | 85% | 🟡 Important |
| Utils | 100% | 🟡 Important |
| Error handling | 90% | 🟡 Important |
Overall Target: 92%
8. Security Testing
Security Tests Required:
✅ MUST TEST:
1. XSS Prevention
- Test: Input with <script> tags is sanitized
2. SQL Injection Prevention
- Test: Input with SQL commands is escaped
3. CSRF Protection
- Test: Requests without token are rejected
4. Rate Limiting
- Test: Too many login attempts are blocked
5. Password Security
- Test: Weak passwords are rejected
- Test: Passwords are not logged
6. Token Security
- Test: Expired tokens are rejected
- Test: Tokens are httpOnly cookies (if applicable)
7. Session Fixation
- Test: New session created after login9. Performance Testing
Performance Benchmarks:
- Login API call: < 500ms
- Token validation: < 50ms
- Form validation: < 16ms (instant)
- Protected route check: < 100ms
Test approach:
- Use performance.now() to measure
- Fail if exceeds budget
- Run in CI on every PR10. Accessibility Testing
A11y Requirements:
✅ Keyboard Navigation
- Tab through all form fields
- Submit with Enter key
- Focus visible
✅ Screen Reader Support
- Form labels associated
- Error messages announced
- Success messages announced
✅ ARIA Attributes
- aria-invalid on error
- aria-describedby for hints
- role="alert" for errors
Test with:
- jest-axe (automated)
- Manual screen reader testing11. CI/CD Integration
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run test:unit
- run: npm run test:coverage
- uses: codecov/codecov-action@v2
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run test:integration
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run test:e2e
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm audit
- run: npm run test:securityQuality Gates:
- All tests must pass
- Coverage > 90%
- No security vulnerabilities
- Performance budgets met
12. Timeline & Effort
Week 1: Foundation
- Day 1-2: Setup test infrastructure
- Day 3-4: Unit tests (functions, utils)
- Day 5: Unit tests (simple components)
Week 2: Components
- Day 1-2: LoginForm tests
- Day 3-4: RegisterForm tests
- Day 5: useAuth hook tests
Week 3: Integration & E2E
- Day 1-2: Integration tests
- Day 3: E2E tests
- Day 4: Security & a11y tests
- Day 5: Documentation & cleanup
Total: 15 working days (3 weeks)
13. Maintenance Strategy
Ongoing:
- [ ] Add tests for every bug fix
- [ ] Update tests when features change
- [ ] Review test coverage weekly
- [ ] Fix flaky tests within 24h
- [ ] Run E2E tests before every release
Monthly:
- [ ] Review test performance
- [ ] Update mocks to match API changes
- [ ] Audit security tests
- [ ] Update documentation14. Success Metrics
Before Ship:
- [ ] 45 tests written and passing
- [ ] 92%+ code coverage
- [ ] 0 security vulnerabilities
- [ ] All E2E tests pass 99%+ of time
- [ ] Test suite runs in < 5 minutes
Post-Ship:
- [ ] 0 production auth bugs in first month
- [ ] Test suite stays under 5 minutes
- [ ] Coverage stays above 90%
- [ ] All tests pass on every commit
15. Risk Management
| Risk | Impact | Mitigation |
|---|---|---|
| Flaky E2E tests | High | Retry logic, better waits |
| API changes | Medium | Contract testing |
| Slow test suite | Medium | Parallel execution |
| Mock drift | Low | Regular mock updates |
| Security gaps | High | Security audit checklist |
Appendix: Test Utilities
// tests/setup/test-utils.js
import { render } from '@testing-library/react';
import { AuthProvider } from '@/context/AuthContext';
// Custom render with providers
export const renderWithAuth = (ui, options) => {
const Wrapper = ({ children }) => <AuthProvider>{children}</AuthProvider>;
return render(ui, { wrapper: Wrapper, ...options });
};
// Mock authenticated user
export const mockUser = {
id: 1,
email: 'test@example.com',
name: 'Test User',
role: 'user',
};
// Mock API responses
export const mockAuthResponses = {
loginSuccess: {
user: mockUser,
token: 'mock-jwt-token',
},
loginError: {
error: 'Invalid credentials',
},
};Total Document: ~2500 wordsImplementation Time: 3 weeksMaintenance Time: 2-3 hours/week
📊 PHẦN 4: SO SÁNH TESTING STRATEGIES (30 phút)
4.1 Testing Approaches Comparison
Approach 1: Test Everything
Strategy: Aim for 100% code coverage, test every function
Pros:
✅ High code coverage number
✅ Every line executed in tests
✅ Feels "safe"
Cons:
❌ Waste time testing trivial code
❌ Brittle tests (break on refactors)
❌ False confidence
❌ Slow test suite
❌ High maintenance cost
When to use:
- Critical security code
- Payment processing
- Medical/financial systems
When NOT to use:
- Typical web apps
- Startups (ship fast)
- Proof of conceptsApproach 2: Test Nothing (YOLO)
Strategy: Manual testing only, no automated tests
Pros:
✅ Fast initial development
✅ No test maintenance
✅ Simple workflow
Cons:
❌ Regressions slip through
❌ Fear of refactoring
❌ Manual testing slow
❌ Can't scale
❌ Hard to onboard new devs
When to use:
- Quick prototypes
- Throwaway code
- Solo projects you won't maintain
When NOT to use:
- Production apps
- Team projects
- Long-term productsApproach 3: Strategic Testing (Recommended)
Strategy: Test critical paths, skip trivial code
What to test:
✅ Business logic (money, auth, etc.)
✅ Complex algorithms
✅ User-critical flows
✅ Edge cases that caused bugs
✅ Code you're scared to change
What to skip:
❌ Trivial getters/setters
❌ Third-party library wrappers
❌ Simple UI components (static)
❌ Configuration files
❌ Constants
Pros:
✅ High ROI tests
✅ Fast test suite
✅ Low maintenance
✅ Tests that matter
✅ Confidence where needed
Cons:
⚠️ Requires judgment
⚠️ Coverage number might be "low"
When to use:
✅ Most production apps
✅ Startups scaling up
✅ Team projects4.2 Mocking Strategies
STRATEGY 1: Mock Everything
━━━━━━━━━━━━━━━━━━━━━━━━
Example:
test('user profile loads', () => {
const mockFetch = jest.fn(() => Promise.resolve(mockData));
const mockUseState = jest.fn();
const mockUseEffect = jest.fn();
// Mock everything!
});
Pros: Isolated, deterministic
Cons: Not testing real behavior, brittle
━━━━━━━━━━━━━━━━━━━━━━━━
STRATEGY 2: Mock Nothing
━━━━━━━━━━━━━━━━━━━━━━━━
Example:
test('user profile loads', () => {
// Hit real API
render(<UserProfile userId={1} />);
// Test with real backend
});
Pros: Tests real behavior
Cons: Slow, flaky, depends on backend
━━━━━━━━━━━━━━━━━━━━━━━━
STRATEGY 3: Mock Boundaries (Recommended)
━━━━━━━━━━━━━━━━━━━━━━━━
Example:
test('user profile loads', () => {
// Mock at API boundary (MSW)
server.use(
rest.get('/api/users/1', (req, res, ctx) => {
return res(ctx.json(mockUser));
})
);
// Real React, real hooks, real components
render(<UserProfile userId={1} />);
});
Pros: Fast, deterministic, realistic
Cons: Need to maintain mocks
RULE OF THUMB:
✅ Mock: Network, Database, External APIs
❌ Don't mock: React, Your code, Utilities4.3 Decision Matrix
| Scenario | Unit | Integration | E2E | Notes |
|---|---|---|---|---|
| Pure function (calculateTotal) | ✅ | ❌ | ❌ | Fast, isolated |
| Custom hook (useAuth) | ✅ | ✅ | ❌ | Both useful |
| Form validation | ✅ | ✅ | ❌ | Unit for logic, integration for UI |
| API integration | ❌ | ✅ | ❌ | Mock at boundary |
| Critical user flow | ❌ | ✅ | ✅ | E2E for confidence |
| Static component | ❌ | ❌ | ❌ | Not worth testing |
| Third-party lib | ❌ | ❌ | ❌ | Trust the library |
🧪 PHẦN 5: DEBUG LAB (20 phút)
Bug 1: Flaky Test
Problematic Test:
test('shows notification after 2 seconds', () => {
render(<NotificationComponent />);
// Wait 2 seconds
setTimeout(() => {
expect(screen.getByText('Notification!')).toBeInTheDocument();
}, 2000);
});
// ❌ PROBLEM: Test sometimes passes, sometimes fails
// Why? setTimeout in test doesn't wait!Questions:
- Tại sao test này flaky?
- Làm sao fix?
- Best practice cho async testing?
💡 Solution
❌ PROBLEM:
Test code doesn't actually wait for setTimeout!
- Test finishes immediately
- Assertion runs before notification appears
- Race condition
✅ FIX:
Option 1: Use testing library's async utilities
```jsx
test('shows notification after delay', async () => {
render(<NotificationComponent />);
// Wait for element to appear
const notification = await screen.findByText('Notification!');
expect(notification).toBeInTheDocument();
});
```
Option 2: Mock timers
```jsx
test('shows notification after 2 seconds', () => {
jest.useFakeTimers();
render(<NotificationComponent />);
// Fast-forward time
jest.advanceTimersByTime(2000);
expect(screen.getByText('Notification!')).toBeInTheDocument();
jest.useRealTimers();
});
```
🎯 BEST PRACTICES:
1. Never use real setTimeout in tests
2. Use findBy\* for async elements
3. Use waitFor for complex waits
4. Mock timers when testing time-based code
5. Always cleanup (useRealTimers)Bug 2: False Positive Test
function addToCart(item) {
// BUG: Function does nothing!
console.log('Adding item:', item);
}
test('adds item to cart', () => {
const item = { id: 1, name: 'Product' };
addToCart(item);
expect(true).toBe(true); // ✅ Test passes!
});
// ❌ PROBLEM: Test passes but function is broken!Questions:
- Tại sao test pass nhưng code broken?
- Làm sao fix test này?
- How to prevent false positives?
💡 Solution
❌ PROBLEM:
Test doesn't actually verify anything!
- Expects true === true (always passes)
- Doesn't check if item was added
- False positive
✅ FIX:
```jsx
// First, fix the function:
let cart = [];
function addToCart(item) {
cart.push(item);
}
// Then, fix the test:
test('adds item to cart', () => {
const item = { id: 1, name: 'Product' };
// Clear cart before test
cart = [];
// Act
addToCart(item);
// Assert the ACTUAL behavior
expect(cart).toContain(item);
expect(cart.length).toBe(1);
});
```
🎯 PREVENTION:
1. Always assert meaningful things
2. Avoid `expect(true).toBe(true)`
3. Test actual outputs
4. Use specific matchers
5. Think: "What would break if this fails?"
Common false positive patterns:
❌ `expect(true).toBeTruthy()`
❌ `expect(result).toBeDefined()` (without checking value)
❌ `expect(mockFn).toHaveBeenCalled()` (without checking args)
Better:
✅ `expect(cart).toContain(item)`
✅ `expect(result).toBe(expectedValue)`
✅ `expect(mockFn).toHaveBeenCalledWith(specificArgs)`Bug 3: Over-mocking
function fetchUserProfile(userId) {
return fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => formatUserData(data));
}
test('fetches user profile', () => {
// Mock EVERYTHING
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockData),
}),
);
const formatUserData = jest.fn((data) => data);
const result = fetchUserProfile(1);
expect(result).toBeDefined();
});
// ❌ PROBLEM: Mocking too much, not testing real behaviorQuestions:
- What's wrong with this test?
- What's NOT being tested?
- How to improve?
💡 Solution
❌ PROBLEMS:
1. Mocking formatUserData
- Not testing that it's called!
- Not testing its logic!
2. Over-mocking fetch
- Too much setup
- Hard to maintain
3. Weak assertion
- expect(result).toBeDefined() tells us nothing
✅ BETTER APPROACH:
Option 1: Mock at API boundary (MSW)
```jsx
import { rest } from 'msw';
import { server } from './mocks/server';
test('fetches and formats user profile', async () => {
// Mock API response
server.use(
rest.get('/api/users/1', (req, res, ctx) => {
return res(
ctx.json({
id: 1,
first_name: 'John',
last_name: 'Doe',
}),
);
}),
);
// Test real function
const result = await fetchUserProfile(1);
// Assert actual output
expect(result).toEqual({
id: 1,
fullName: 'John Doe', // formatUserData runs for real!
});
});
```
Option 2: Separate concerns
```jsx
// Test fetch separately
test('fetchUser calls correct endpoint', async () => {
// Mock fetch (boundary)
// Test URL, method, headers
});
// Test formatting separately (unit test)
test('formatUserData formats correctly', () => {
const input = { first_name: 'John', last_name: 'Doe' };
const output = formatUserData(input);
expect(output).toEqual({ fullName: 'John Doe' });
});
```
🎯 GUIDELINES:
Mock:
✅ Network calls (fetch, axios)
✅ External services (analytics, payments)
✅ File system
✅ Date/time (for determinism)
Don't mock:
❌ Your own functions (test them!)
❌ React hooks
❌ Utilities you wrote
❌ Everything "just because"
RULE: Mock at boundaries, test real behavior✅ PHẦN 6: TỰ ĐÁNH GIÁ (15 phút)
Knowledge Check
Testing Philosophy:
- [ ] Tôi hiểu Testing Pyramid và tại sao nó quan trọng
- [ ] Tôi biết phân biệt Unit vs Integration vs E2E tests
- [ ] Tôi biết test behavior, không test implementation
- [ ] Tôi hiểu trade-offs giữa different testing strategies
- [ ] Tôi biết khi nào NÊN và KHÔNG NÊN viết tests
Testing Practices:
- [ ] Tôi có thể viết descriptive test names
- [ ] Tôi hiểu và áp dụng AAA pattern
- [ ] Tôi biết identify test scenarios từ requirements
- [ ] Tôi biết design test suite với appropriate coverage
- [ ] Tôi hiểu mocking strategy (mock gì, không mock gì)
Critical Thinking:
- [ ] Tôi có thể critique bad tests và giải thích tại sao
- [ ] Tôi có thể prioritize tests (critical vs nice-to-have)
- [ ] Tôi hiểu false positives và false negatives
- [ ] Tôi biết identify và fix flaky tests
- [ ] Tôi có thể estimate testing effort
Self-Assessment Quiz
Question 1: Bạn có 100 components trong app. Product Manager muốn 100% test coverage. Bạn phản hồi như thế nào?
Expected Answer
✅ GOOD RESPONSE:
"100% coverage không phải best goal vì:
1. Not all code is equal:
- Static components: Low value
- Business logic: High value
2. Better approach:
- Identify critical paths (checkout, payments, auth)
- Target 100% on those
- Accept 70-80% overall coverage
3. ROI perspective:
- Last 20% coverage takes 80% effort
- Better use that time on features
Let's prioritize:
- Critical features: 100% coverage
- Important features: 80% coverage
- Nice-to-have: 50% coverage
This gives us confidence where it matters."
❌ BAD RESPONSES:
"OK, I'll test everything" → Waste of time
"Coverage doesn't matter" → Too cavalier
"100% coverage guarantees no bugs" → False!Question 2: Test suite mất 30 phút để chạy. Developers không chạy tests trước commit. Làm sao fix?
Expected Answer
✅ SOLUTIONS:
Short-term:
1. Parallel execution
- Run tests in parallel
- Use CI parallelization
2. Smart test selection
- Only run affected tests
- Full suite on CI, not local
3. Split by speed
- Fast tests (< 5min): Run locally
- Slow tests: Run on CI only
Long-term:
1. Identify slow tests
- Profile with --verbose
- Find bottlenecks
2. Optimize slow tests
- Too many E2E? → Convert to integration
- Real API calls? → Mock with MSW
- Large datasets? → Use smaller fixtures
3. Architecture
- More unit tests (fast)
- Fewer E2E tests (slow)
- Follow pyramid
Goal: < 5 minutes locally, < 15 minutes on CI
Rule: If tests are slow, developers won't run them.
Make it fast!🏠 BÀI TẬP VỀ NHÀ
Bắt buộc (45 phút)
Exercise 1: Analyze Your Own Code
Lấy project từ Ngày 52 (Modern React App), và:
List Test Scenarios (15 phút)
- Identify 10 test scenarios
- Categorize: Unit/Integration/E2E
- Prioritize: Critical/Important/Nice-to-have
Write Test Descriptions (20 phút)
- Choose 5 critical scenarios
- Write full test descriptions (AAA format)
- Include edge cases
Estimate Effort (10 phút)
- Estimate time for each test
- Calculate total testing time
- Justify the ROI
Deliverable: Markdown document với test plan
Nâng cao (90 phút)
Exercise 2: Design Full Test Strategy
Cho feature "E-commerce Product Page":
Features:
- Product details display
- Image gallery
- Add to cart (with quantity)
- Size/color selection
- Reviews section
- Recommended products
- Stock availability check
Requirements:
1. Design complete test strategy (unit + integration + E2E)
2. Write all test descriptions (AAA ts-code)
3. Define mocking strategy
4. Estimate coverage targets
5. Identify critical paths
6. Create test timeline
7. Define success metricsDeliverable: Complete test strategy document (similar to Level 5 solution)
📚 TÀI LIỆU THAM KHẢO
Bắt buộc đọc
Kent C. Dodds - Testing Trophy:
- https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications
- Alternative to Testing Pyramid
Martin Fowler - Test Pyramid:
- https://martinfowler.com/articles/practical-test-pyramid.html
- Original pyramid concept, in-depth
Testing Library - Guiding Principles:
- https://testing-library.com/docs/guiding-principles
- Test behavior, not implementation
Đọc thêm
Google Testing Blog:
- https://testing.googleblog.com/
- Real-world testing at scale
Kent C. Dodds - Common Testing Mistakes:
Test Desiderata:
- https://kentbeck.github.io/TestDesiderata/
- Properties of good tests
🔗 KẾT NỐI KIẾN THỨC
Kiến thức nền:
Từ toàn bộ Phase 1-5:
- Tất cả React concepts (sẽ test chúng)
- useState, useEffect, custom hooks
- Component patterns
- Error handling
- Async operations
Concepts mới hôm nay:
- Testing Pyramid
- AAA Pattern
- Mocking strategies
- Test behavior, not implementation
Hướng tới (sẽ dùng trong):
Ngày 54-57 (Testing thực hành):
- Ngày 54: React Testing Library basics
- Ngày 55: Testing hooks & context
- Ngày 56: Mocking API với MSW
- Ngày 57: Integration & E2E preview
Capstone Project (Ngày 61-75):
- Full test suite
- CI/CD integration
- Production-grade testing
💡 SENIOR INSIGHTS
Cân Nhắc Production
1. The Coverage Trap:
❌ MYTH: "100% coverage = no bugs"
✅ REALITY:
- Coverage measures lines executed, not correctness
- Can have 100% coverage with bad tests
- Can have bugs in 100% covered code
Example:
function divide(a, b) {
return a / b; // ✅ 100% covered
}
test('divides numbers', () => {
expect(divide(10, 2)).toBe(5); // ✅ Passes
});
// 🐛 BUG: divide(10, 0) = Infinity (not caught!)
Better goal: "Meaningful tests of critical paths"2. Testing in Agile:
SCENARIO: Product wants feature tomorrow
Option 1: Skip tests, ship fast
- ✅ Ship today
- ❌ Bugs in production
- ❌ Fear of refactoring
- ❌ Technical debt
Option 2: Write all tests, ship slow
- ❌ Miss deadline
- ✅ High quality
- ❌ Opportunity cost
Option 3: Strategic testing (RECOMMENDED)
- ✅ Ship on time
- ✅ Critical paths covered
- ✅ Can refactor safely
- ⚠️ Accept some risk on edge cases
RULE: Test critical path, ship, iterate3. Test Maintenance Cost:
REALITY: Tests are code that needs maintenance
Costs:
- ⏱️ Time to write (20-30% of dev time)
- 🔧 Time to maintain (breaks on refactors)
- 🏃 Time to run (slows feedback loop)
- 🧠 Cognitive load (understand test code)
ROI Calculation:
- Cost of writing test: 30 minutes
- Cost of manual testing: 5 minutes each time
- Test runs: 100 times
Break-even: 6 manual tests
After that: Pure profit!
INSIGHT: More tests ≠ better
Right tests = better4. Team Dynamics:
COMMON SCENARIO:
Team A: "100% coverage or no merge"
- Result: Developers game the system
- Tests become checkbox exercise
- Quality suffers
Team B: "No coverage requirement"
- Result: No one writes tests
- Production bugs
- Fear of changes
Team C: "Test critical paths, review together"
- Result: Meaningful tests
- Team learns from each other
- Balance speed & quality
INSIGHT: Culture > Coverage numberCâu Hỏi Phỏng Vấn
Junior Level:
Q1: "Giải thích sự khác biệt giữa Unit test và Integration test?"
Expected Answer:
Unit Test:
- Test 1 function/component in isolation
- Fast (< 10ms)
- No dependencies
- Example: Test calculateTotal() function
Integration Test:
- Test multiple components working together
- Medium speed (100-500ms)
- Has dependencies (may use real API with mocks)
- Example: Test form submission → API call → success message
When to use:
- Unit: Business logic, utilities, pure functions
- Integration: User workflows, critical pathsQ2: "Tại sao không nên test implementation details?"
Expected Answer:
Problem với testing implementation:
- Tests break on refactor (even if behavior same)
- False confidence (passes but feature broken)
- Expensive to maintain
Example:
❌ Bad: Test that component uses useState
✅ Good: Test that button click updates displayed count
Reason: User doesn't care HOW you store state,
they care that clicking button works!
Rule: Test inputs & outputs, not internalsMid Level:
Q3: "Làm sao quyết định mock gì và không mock gì?"
Expected Answer:
✅ MOCK:
- Network calls (fetch, axios)
→ Don't hit real API
→ Use MSW for realistic mocks
- External services (payments, analytics)
→ Don't charge cards in tests!
- Time (Date.now, setTimeout)
→ Tests should be deterministic
- Browser APIs (localStorage, geolocation)
→ Not available in test environment
❌ DON'T MOCK:
- Your own code
→ You need to test it!
- React hooks (useState, useEffect)
→ Test real behavior
- Utilities you wrote
→ Integration test them
RULE: Mock at boundaries, test real behavior insideQ4: "Test suite chạy 1 giờ. Làm sao optimize?"
Expected Answer:
DIAGNOSIS:
1. Profile tests (find slow ones)
2. Check test distribution (too many E2E?)
3. Look for unnecessary waits
SOLUTIONS:
Short-term:
- Run tests in parallel
- Split into fast/slow suites
- Smart test selection (only affected tests)
Long-term:
- Convert E2E → Integration (10x faster)
- Mock expensive operations
- Reduce test data size
- Fix anti-patterns (real API calls, long timeouts)
Architecture:
- Follow pyramid (70% unit, 20% integration, 10% E2E)
- Unit tests should be < 5 min
- Full suite should be < 15 min
GOAL: Fast feedback loop = developers run testsSenior Level:
Q5: "Thiết kế testing strategy cho microservices app?"
Expected Answer:
CHALLENGES:
- Multiple services
- Async communication
- Distributed system
- Complex dependencies
STRATEGY:
1. Service Level (70%):
- Unit tests (business logic)
- Integration tests (DB, message queue)
- Contract tests (API contracts)
- Don't mock other services
2. Integration Level (20%):
- Test service pairs
- Mock downstream services
- Test error scenarios (service down)
3. End-to-End (10%):
- Critical business flows only
- Full stack, all services
- Expensive, run less frequently
CONTRACT TESTING:
- Use Pact or similar
- Provider tests (API matches contract)
- Consumer tests (uses API correctly)
- Prevents integration issues
MONITORING:
- Tests only prove it worked ONCE
- Production monitoring proves it works NOW
- Combine: Tests + ObservabilityQ6: "Convince team to invest in testing?"
Expected Answer:
PITCH TO STAKEHOLDERS:
For Business:
"Testing reduces bug fix cost 10x:
- Bug in dev: 1 hour fix
- Bug in production: 10 hours fix + customer impact
ROI: After 2-3 months, testing SAVES time"
For Developers:
"Testing enables:
- Fearless refactoring
- Faster debugging (tests pinpoint issues)
- Better code design (testable = modular)
- Documentation (tests show usage)"
For Product:
"Testing enables:
- Ship faster (confidence to deploy)
- Better UX (fewer bugs)
- More features (less time fixing bugs)"
STRATEGY:
- Start small (critical paths only)
- Show results (bugs caught)
- Build culture gradually
- Celebrate wins (tests catching bugs)
WRONG APPROACH:
❌ "We need 100% coverage"
❌ "Industry standard"
❌ Force without buy-in
RIGHT APPROACH:
✅ "Tests save us time"
✅ "Tests caught bugs"
✅ "Tests enable features"War Stories
Story 1: The 100% Coverage Disaster
🔥 THE SITUATION:
CTO mandated: "100% coverage or PR rejected"
WHAT HAPPENED:
- Developers wrote useless tests
test('component exists', () => {
expect(wrapper.exists()).toBe(true);
});
- Coverage: 100% ✅
- Quality: Terrible ❌
- Bugs still shipped
- Team morale down
💡 LESSON:
"Coverage number means nothing.
Test quality matters more.
Better: 60% coverage of RIGHT things."
FIX:
- Changed to: "Critical paths tested"
- Code review includes test quality
- Tests must fail if feature breaksStory 2: The Flaky E2E Hell
🔥 THE SITUATION:
E2E test suite: 45 minutes, 30% failure rate
DEVELOPER BEHAVIOR:
- "Just re-run it"
- Skip tests to merge faster
- Trust erodes
- Tests become useless
ROOT CAUSES:
- Network timeouts
- Race conditions
- Shared test database
- Hardcoded waits
💡 LESSON:
"One flaky test destroys trust in entire suite.
Fix immediately or delete."
FIX:
- Converted 70% E2E → Integration tests
- Fixed race conditions (proper waits)
- Isolated test data
- Suite now: 8 minutes, 99% stableStory 3: The Mocking Madness
🔥 THE SITUATION:
Test had 100 lines of mocks, 2 lines of assertion
const mockFetch = jest.fn();
const mockUseState = jest.fn();
const mockUseEffect = jest.fn();
const mockComponent = jest.fn();
// ... 90 more lines
💥 PROBLEMS:
- Tests broke on every refactor
- Spent more time fixing tests than code
- Mocks drifted from reality
- False confidence
💡 LESSON:
"Over-mocking tests implementation, not behavior.
Mock boundaries, test real code."
FIX:
- Switched to MSW (mock API, not React)
- Used real components
- Tests now: 10 lines, more valuable🎯 PREVIEW NGÀY MAI
Ngày 54: React Testing Library - Basics
Ngày mai chúng ta chuyển từ theory sang practice! Bạn sẽ:
- Setup Jest + React Testing Library
- Viết tests đầu tiên cho React components
- Learn queries (getBy, findBy, queryBy)
- Test user interactions (click, type, submit)
- Understand async testing
Tất cả philosophy học hôm nay sẽ được áp dụng thực tế. Chuẩn bị viết LOTS of code!
Chuẩn bị:
- Ôn lại AAA pattern
- Review project Ngày 52 (sẽ viết tests cho nó)
- Mindset shift: "How would a USER test this?" not "How would a DEVELOPER test this?"
✅ Hoàn thành Ngày 53!
Chúc mừng! Bạn đã có foundation vững chắc về testing philosophy. Ngày mai sẽ hands-on và practical hơn rất nhiều. Remember:
Key Takeaways:
- Test behavior, not implementation
- Follow the pyramid (70/20/10)
- AAA pattern always
- Mock at boundaries
- Quality > Coverage number
Mindset: Testing không phải "chore" - nó là investment vào future productivity! 🎯