Skip to content

Portal Testing

The borrower portal uses Vitest with Testing Library for component testing.

Setup

cd frontend/borrower
npm run test        # Run all tests
npm run test -- --watch  # Watch mode

Tests run in a jsdom environment via Vitest.

Test Utilities

test-utils.tsx provides a renderWithProviders() helper that wraps components in all required providers.

renderWithProviders(ui, options?)

import { renderWithProviders } from "@/test-utils";

test("renders dashboard", () => {
  renderWithProviders(<DashboardPage />);
});

Default providers applied:

  • QueryClientProvider: createTestQueryClient() with retry: false
  • MemoryRouter: React Router for route-dependent components
  • ThemeProvider: MUI theme
  • AuthContext: Authenticated with a default borrower

Default Test Borrower

const defaultBorrower: BorrowerProfile = {
  id: "test-id",
  first_name: "Jane",
  last_name: "Doe",
  email: "jane@example.com",
  phone: "+12125551234",
  date_of_birth: "1990-01-15",
  ssn_last_four: "1234",
  language_preference: "en",
  communication_preference: "email",
};

Overriding Auth State

Pass custom auth state for testing unauthenticated or different user scenarios:

renderWithProviders(<LoginPage />, {
  auth: {
    borrower: null,
    isAuthenticated: false,
    isLoading: false,
    login: vi.fn(),
    logout: vi.fn(),
  },
});

Custom Routes

For testing route-dependent components:

renderWithProviders(<LoanDetailPage />, {
  routes: ["/loans/test-loan-id"],
});

Writing Tests

Page Component Tests

import { screen } from "@testing-library/react";
import { renderWithProviders } from "@/test-utils";
import DashboardPage from "@/pages/DashboardPage";

test("shows welcome message", () => {
  renderWithProviders(<DashboardPage />);
  expect(screen.getByText(/Welcome, Jane/)).toBeInTheDocument();
});

Component Tests

import { screen } from "@testing-library/react";
import { renderWithProviders } from "@/test-utils";
import MoneyDisplay from "@/components/MoneyDisplay";

test("formats currency", () => {
  renderWithProviders(
    <MoneyDisplay value={{ amount: "1234.56", currency: "USD" }} />,
  );
  expect(screen.getByText("$1,234.56")).toBeInTheDocument();
});

Testing User Interactions

import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "@/test-utils";

test("submits login form", async () => {
  const mockLogin = vi.fn();
  renderWithProviders(<LoginPage />, {
    auth: { ...defaultAuth, login: mockLogin, isAuthenticated: false, borrower: null },
  });

  await userEvent.type(screen.getByLabelText(/email/i), "test@example.com");
  await userEvent.type(screen.getByLabelText(/password/i), "password123");
  await userEvent.click(screen.getByRole("button", { name: /sign in/i }));

  await waitFor(() => {
    expect(mockLogin).toHaveBeenCalledWith("test@example.com", "password123");
  });
});

Test Organization

frontend/borrower/src/
├── pages/__tests__/         # Page component tests
├── components/__tests__/    # Shared component tests
└── hooks/__tests__/         # Hook tests (if any)

See Also