When components use things like useLoaderData
, <Link>
, etc, they are required to be rendered in context of a React Router app. The createRoutesStub
function creates that context to test components in isolation.
Consider a login form component that relies on useActionData
import { useActionData } from "react-router";
export function LoginForm() {
const errors = useActionData();
return (
<Form method="post">
<label>
<input type="text" name="username" />
{errors?.username && <div>{errors.username}</div>}
</label>
<label>
<input type="password" name="password" />
{errors?.password && <div>{errors.password}</div>}
</label>
<button type="submit">Login</button>
</Form>
);
}
We can test this component with createRoutesStub
. It takes an array of objects that resemble route modules with loaders, actions, and components.
import { createRoutesStub } from "react-router";
import {
render,
screen,
waitFor,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";
test("LoginForm renders error messages", async () => {
const USER_MESSAGE = "Username is required";
const PASSWORD_MESSAGE = "Password is required";
const Stub = createRoutesStub([
{
path: "/login",
Component: LoginForm,
action() {
return {
errors: {
username: USER_MESSAGE,
password: PASSWORD_MESSAGE,
},
};
},
},
]);
// render the app stub at "/login"
render(<Stub initialEntries={["/login"]} />);
// simulate interactions
userEvent.click(screen.getByText("Login"));
await waitFor(() => screen.findByText(USER_MESSAGE));
await waitFor(() => screen.findByText(PASSWORD_MESSAGE));
});