Test theo đúng thao tác người dùng: điền sai → submit → assert thông báo lỗi hiển thị và callback submit không được gọi.
Không đụng vào state nội bộ hay method của thư viện form.
tsx
it("shows validation errors and does not submit", async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render(<SignUpForm onSubmit={onSubmit} />)
await user.type(screen.getByLabelText(/email/i), "not-an-email")
await user.type(screen.getByLabelText(/password/i), "123")
await user.click(screen.getByRole("button", { name: /sign up/i }))
expect(await screen.findByText(/email is invalid/i)).toBeInTheDocument()
expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument()
expect(onSubmit).not.toHaveBeenCalled()
})Lưu ý:
- findBy cho lỗi đầu tiên vì validate thường async (resolver của zod/yup chạy sau một tick).
- Query input bằng getByLabelText — nếu query fail nghĩa là input thiếu <label htmlFor>, đó cũng là bug thật.
- Thêm một test happy path: điền hợp lệ, assert onSubmit nhận đúng payload. Đây mới là phần hay hỏng khi refactor.
- Nếu message lỗi được gắn role="alert" thì assert bằng getByRole("alert"), vừa kiểm tra nội dung vừa kiểm tra screen reader đọc được.