← Back to list

testing
by oakoss
Open-source SaaS starter kit with React, TanStack, and Better Auth
⭐ 0🍴 0📅 Jan 26, 2026
SKILL.md
name: testing description: Write tests with Vitest and Testing Library. Use when creating tests, writing specs, mocking, or setting up test coverage.
Vitest + Testing Library
Running Tests
pnpm test # Run all
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage
Basic Test
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { Button } from '@oakoss/ui';
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>);
expect(
screen.getByRole('button', { name: /click me/i }),
).toBeInTheDocument();
});
});
User Interactions
import userEvent from '@testing-library/user-event';
it('handles click', async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(<Button onPress={onClick}>Click</Button>);
await user.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalled();
});
Query Priority
getByRole- Accessible elementsgetByLabelText- Form inputsgetByText- Text contentgetByTestId- Last resort
Mocking
const mockFn = vi.fn().mockReturnValue('result');
vi.mock('@oakoss/ui', () => ({
Button: vi.fn(({ children }) => <button>{children}</button>),
}));
const spy = vi.spyOn(utils, 'cn');
Async
await screen.findByText('Loaded');
await waitFor(() => {
expect(screen.getByRole('list')).toHaveTextContent('Item');
});
Testing TanStack Query
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
function createTestQueryClient() {
return new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: 0 },
mutations: { retry: false },
},
});
}
function renderWithQuery(ui: React.ReactElement) {
const queryClient = createTestQueryClient();
return render(
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
);
}
it('loads and displays data', async () => {
vi.mock('@/lib/api', () => ({
getPosts: vi.fn().mockResolvedValue([{ id: '1', title: 'Test' }]),
}));
renderWithQuery(<PostList />);
expect(await screen.findByText('Test')).toBeInTheDocument();
});
Testing TanStack Form
import userEvent from '@testing-library/user-event';
it('validates and submits form', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
it('shows validation errors', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
});
Testing with Router Context
import { RouterProvider, createMemoryHistory } from '@tanstack/react-router';
function renderWithRouter(ui: React.ReactElement, { route = '/' } = {}) {
const history = createMemoryHistory({ initialEntries: [route] });
const router = createRouter({ routeTree, history });
return render(<RouterProvider router={router}>{ui}</RouterProvider>);
}
it('navigates on click', async () => {
const user = userEvent.setup();
renderWithRouter(<Navigation />, { route: '/' });
await user.click(screen.getByRole('link', { name: /about/i }));
expect(window.location.pathname).toBe('/about');
});
Mocking Server Functions
vi.mock('@/lib/server-functions', () => ({
getUser: vi.fn(),
updateUser: vi.fn(),
}));
import { getUser, updateUser } from '@/lib/server-functions';
beforeEach(() => {
vi.mocked(getUser).mockResolvedValue({ id: '1', name: 'Test User' });
});
it('loads user data', async () => {
render(<UserProfile userId="1" />);
expect(await screen.findByText('Test User')).toBeInTheDocument();
expect(getUser).toHaveBeenCalledWith({ data: { id: '1' } });
});
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Using getBy for async content | Use findBy or waitFor for async |
| Testing implementation details | Test behavior, not internal state |
Using getByTestId first | Prefer getByRole, getByLabelText |
Missing userEvent.setup() | Always call userEvent.setup() first |
| Not wrapping in act | Use userEvent which handles this |
| Mocking too much | Only mock external dependencies |
| Not cleaning up mocks | Use vi.clearAllMocks() in beforeEach |
| Shared QueryClient in tests | Create fresh QueryClient per test |
| Retry enabled in test queries | Set retry: false in test QueryClient |
| Testing third-party components | Trust the library, test your usage |
Delegation
- Test discovery: For finding untested code paths, use
Exploreagent - Coverage analysis: For comprehensive coverage review, use
Taskagent - Code review: After writing tests, delegate to
code-revieweragent
Related Skills
| Task | Skill |
|---|---|
| Query testing patterns | tanstack-query |
| Form testing patterns | tanstack-form |
| Router testing patterns | tanstack-router |
| Error boundary testing | error-boundaries |
| Integration flows | integration-patterns |
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon

