React Testing Library
Component and hook tests use Vitest with React Testing Library. All shared hooks are tested in packages/shared/src/__tests__/, while component tests live alongside their respective packages.
How To Approach Tests
React Testing Library encourages testing components the way users interact with them — through accessible queries and real DOM behavior, not implementation details. The philosophy: if a user can't see it or interact with it, the test shouldn't depend on it.
Test Environment
Tests use @vitest-environment jsdom for browser API emulation. Each test file can specify its environment via a docblock comment at the top:
/**
* @vitest-environment jsdom
*/
The beforeEach block should call vi.clearAllMocks() to reset mock state between tests.
Test Utilities
The shared test utilities in packages/shared/src/__tests__/test-utils/ provide wrappers that set up the provider tree needed by most hooks and components.
createTestWrapper
Wraps components with QueryClientProvider and IntlProvider:
import { createTestWrapper, createTestQueryClient } from '../test-utils';
const queryClient = createTestQueryClient();
const wrapper = createTestWrapper(queryClient);
const { result } = renderHook(() => useMyHook(), { wrapper });
The test QueryClient disables retries, garbage collection, and stale time to make tests deterministic.
renderHookWithProviders
A convenience wrapper that combines renderHook with the full provider setup:
import { renderHookWithProviders } from '../test-utils';
const { result } = renderHookWithProviders(() => useGardenActions(address, chainId));
createIntlWrapper
For hooks that need IntlProvider but not React Query:
import { createIntlWrapper } from '../test-utils';
const { result } = renderHook(() => useTranslation(), {
wrapper: createIntlWrapper(),
});
Completing Test Coverage
Testing Hooks vs Components
Hook Tests
Most business logic lives in hooks within @green-goods/shared. Test hooks by:
- Mocking external dependencies (
vi.mock) - Rendering the hook with
renderHookWithProviders - Asserting on
result.currentvalues and mock call arguments
it("passes correct query key", () => {
useGardenActions(GARDEN, CHAIN_ID);
expect(mockUseQuery).toHaveBeenCalledWith(
expect.objectContaining({
queryKey: ["greengoods", "actions", "byGarden", GARDEN, CHAIN_ID],
})
);
});
Component Tests
Component tests in packages/admin and packages/client focus on rendering and user interaction:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
it("renders the action card", () => {
render(<ActionCard action={mockAction} />, { wrapper: createTestWrapper() });
expect(screen.getByText(mockAction.title)).toBeInTheDocument();
});
Use accessibility-first queries (getByRole, getByLabelText) over getByTestId. Test IDs are a last resort.
Mock Patterns
Module Mocking
Hooks are tested by mocking their dependencies at the module level with vi.mock(). The convention is to declare mocks before imports:
const mockGetGardenActions = vi.fn();
vi.mock("../../../modules/data/greengoods", () => ({
getGardenActions: (...args: unknown[]) => mockGetGardenActions(...args),
}));
const mockUseQuery = vi.fn();
vi.mock("@tanstack/react-query", () => ({
useQuery: (options: any) => mockUseQuery(options),
}));
// Import the hook under test AFTER mocks
import { useGardenActions } from "../../../hooks/action/useGardenActions";
Mock Factories
The mock-factories.ts file exports createMock* factory functions for generating test data with sensible defaults:
import { createMockGarden, createMockAction } from '../test-utils';
const garden = createMockGarden({ name: 'Test Garden' });
const action = createMockAction({ gardenAddress: garden.address });
Always use these factories instead of hand-crafting test objects -- they ensure type safety and keep tests resilient to schema changes.
Running Tests
Tests run through Vitest (never bun's built-in test runner):
# Run all tests across the monorepo
bun run test
# Run tests for a specific package
cd packages/shared && bun run test
cd packages/admin && bun run test
cd packages/client && bun run test
# Watch mode
cd packages/shared && bun run test:watch
Always use bun run test, never bun test. The latter uses bun's built-in runner which ignores vitest configuration, environment setup, and test utilities.
CI Integration
Per-package test workflows run on push/PR to main and develop, triggered by path filters. The shared package workflow additionally generates coverage reports.
Resources
- React Testing Library Documentation -- Official RTL docs
- Testing Library Queries -- Query priority guide
- Test utilities:
packages/shared/src/__tests__/test-utils/ - Mock factories:
packages/shared/src/__tests__/test-utils/mock-factories.ts
Next: Storybook Testing
Learn how to document and visually test components with Storybook.
Storybook Testing