Assert kết quả người dùng (hoặc code gọi) quan sát được, không assert cách bên trong làm ra kết quả đó.
Dấu hiệu test bám implementation:
- Assert vào state nội bộ hoặc hàm private:
expect(component.state.isOpen).toBe(true). - Assert vào số lần gọi của một hàm nội bộ:
expect(calculateTax).toHaveBeenCalledTimes(1). Đổi cách cache là đỏ, dù kết quả vẫn đúng. - Assert vào class/DOM structure:
expect(el.className).toContain('btn-active'). - Refactor thuần tuý (đổi tên hàm, tách module, đổi thư viện) làm test đỏ hàng loạt.
Đổi sang assert hành vi:
ts
// implementation-coupled
expect(cart.state.items.length).toBe(1)
expect(saveToStorage).toHaveBeenCalled()
// behaviour
await user.click(screen.getByRole('button', { name: /add to cart/i }))
expect(screen.getByText('1 item in cart')).toBeVisible()Phép thử nhanh: giữ nguyên hành vi, viết lại toàn bộ phần bên trong — test có còn dùng được không? Nếu phải sửa test thì test đó đang mô tả implementation, và nó sẽ cản trở đúng lúc bạn cần refactor nhất.
Ngoại lệ chính đáng: với side effect ở biên (gọi API thanh toán, gửi email) thì việc assert "đã gọi đúng một lần với payload này" chính là hành vi cần kiểm tra.