I'm trying to test a simple Inertia.js + React component using Vitest and React Testing Library. The component uses useForm() from @inertiajs/react and the route() helper function from Ziggy (Laravel).
Here’s a simplified version of the component(Login.tsx):
import { useForm } from '@inertiajs/react';
export default function Login() {
const { data, setData, post } = useForm({
email: '',
password: '',
});
const submit = (e: React.FormEvent) => {
e.preventDefault();
post(route('login')); // ❌ This line causes ReferenceError
};
return (
<form onSubmit={submit}>
<input
name="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
}
Problem: When running the test:
I got this error: ReferenceError: route is not defined
What I tried
globalThis.route = vi.fn((name: string) => `/${name}`);
Adding this to top of the login.test.tsx file
What is the correct and type-safe way to mock Ziggy’s route() function in Vitest, so that I can test Inertia components that call route('login'), route('password.request'), etc.?
Do I need to mock Ziggy or install anything specific for tests?
This is my Login.test.tsx
import { fireEvent, render, screen } from '@testing-library/react';
import Login from '../../Pages/Auth/Login';
// Mock the Inertia route function
const post = vi.fn();
const setData = vi.fn();
vi.mock('@inertiajs/react', async () => {
const actual = await vi.importActual('@inertiajs/react');
return {
...actual,
__esModule: true,
useForm: vi.fn(() => ({
data: { email: '', password: '', remember: false },
setData,
post,
processing: false,
errors: {},
reset: vi.fn(),
})),
};
});
describe('LoginPage', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders login form fields and button', () => {
render(<Login canResetPassword={true} />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /log in/i }),
).toBeInTheDocument();
expect(screen.getByText(/forgot your password/i)).toBeInTheDocument();
});
it.skip('allows input and submits form', () => {
render(<Login canResetPassword={true} />);
const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement;
const passwordInput = screen.getByLabelText(
/password/i,
) as HTMLInputElement;
const submitButton = screen.getByRole('button', { name: /log in/i });
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
fireEvent.change(passwordInput, { target: { value: 'secret123' } });
expect(setData).toHaveBeenCalledWith('email', '[email protected]');
expect(setData).toHaveBeenCalledWith('password', 'secret123');
fireEvent.click(submitButton);
expect(post).toHaveBeenCalledWith(
'login',
expect.objectContaining({
onFinish: expect.any(Function),
}),
);
});
});