setup-testing
Access: /template setup-testing or /t setup-testing
Configures comprehensive testing infrastructure for your MakerKit application, including unit tests, integration tests, and end-to-end testing.
What It Does
The setup-testing command helps you:
- Configure Jest for unit and integration testing
- Set up Playwright for E2E testing
- Create test utilities and helpers
- Add test database configuration
- Implement CI/CD test pipelines
- Create example test suites
Usage
bash
/template setup-testing "Description"
# or
/t setup-testing "Description"When prompted, specify:
- Testing frameworks to configure
- Test coverage requirements
- CI/CD integration preferences
- Test database setup
Prerequisites
- A MakerKit project with basic structure
- Understanding of testing requirements
- Commercial MakerKit license from MakerKit
What Gets Created
1. Jest Configuration
Unit and integration test setup:
javascript
// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^@kit/(.*)$': '<rootDir>/packages/$1/src',
},
testEnvironment: 'jest-environment-jsdom',
testPathIgnorePatterns: ['/node_modules/', '/.next/', '/e2e/'],
collectCoverageFrom: [
'app/**/*.{js,jsx,ts,tsx}',
'packages/**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!**/.next/**',
'!**/coverage/**',
'!**/jest.config.js',
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70,
},
},
};
module.exports = createJestConfig(customJestConfig);2. Test Setup File
Global test configuration:
typescript
// jest.setup.js
import '@testing-library/jest-dom';
import { TextEncoder, TextDecoder } from 'util';
import { server } from './test/mocks/server';
// Polyfills
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
// Mock environment variables
process.env.NEXT_PUBLIC_SUPABASE_URL = 'http://localhost:54321';
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = 'test-anon-key';
process.env.SUPABASE_SERVICE_ROLE_KEY = 'test-service-key';
// MSW server setup
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// Mock Next.js router
jest.mock('next/navigation', () => ({
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
prefetch: jest.fn(),
}),
usePathname: () => '/test',
useSearchParams: () => new URLSearchParams(),
}));3. Test Utilities
Helper functions for testing:
typescript
// test/utils/test-utils.tsx
import { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createBrowserClient } from '@supabase/ssr';
// Create a test query client
function createTestQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
retry: false,
cacheTime: 0,
},
},
});
}
// Custom render function
function customRender(
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) {
const queryClient = createTestQueryClient();
const supabase = createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<SupabaseProvider client={supabase}>
{children}
</SupabaseProvider>
</QueryClientProvider>
);
}
return render(ui, { wrapper: Wrapper, ...options });
}
// Re-export everything
export * from '@testing-library/react';
export { customRender as render };
// Test data factories
export function createMockUser(overrides?: Partial<User>): User {
return {
id: 'user-123',
email: 'test@example.com',
display_name: 'Test User',
created_at: new Date().toISOString(),
...overrides,
};
}
export function createMockTeam(overrides?: Partial<Team>): Team {
return {
id: 'team-123',
name: 'Test Team',
slug: 'test-team',
created_at: new Date().toISOString(),
...overrides,
};
}4. Mock Service Worker Setup
API mocking for tests:
typescript
// test/mocks/handlers.ts
import { rest } from 'msw';
export const handlers = [
// Auth endpoints
rest.post('/auth/v1/token', (req, res, ctx) => {
return res(
ctx.json({
access_token: 'mock-access-token',
refresh_token: 'mock-refresh-token',
user: createMockUser(),
})
);
}),
// API endpoints
rest.get('/api/v1/teams', (req, res, ctx) => {
return res(
ctx.json({
data: [createMockTeam()],
pagination: { total: 1, limit: 10, offset: 0 },
})
);
}),
// Supabase endpoints
rest.get(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/users`,
(req, res, ctx) => {
return res(ctx.json([createMockUser()]));
}
),
];
// test/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);5. Component Tests
Example component test:
typescript
// components/__tests__/team-switcher.test.tsx
import { render, screen, fireEvent, waitFor } from '@/test/utils/test-utils';
import { TeamSwitcher } from '../team-switcher';
import { server } from '@/test/mocks/server';
import { rest } from 'msw';
describe('TeamSwitcher', () => {
it('displays current team', () => {
render(<TeamSwitcher currentTeam={createMockTeam()} />);
expect(screen.getByText('Test Team')).toBeInTheDocument();
});
it('switches teams on selection', async () => {
const mockPush = jest.fn();
jest.mocked(useRouter).mockReturnValue({
push: mockPush,
} as any);
render(<TeamSwitcher currentTeam={createMockTeam()} />);
// Open dropdown
fireEvent.click(screen.getByRole('button'));
// Click different team
fireEvent.click(screen.getByText('Other Team'));
await waitFor(() => {
expect(mockPush).toHaveBeenCalledWith('/home/other-team');
});
});
it('handles API errors gracefully', async () => {
server.use(
rest.get('/api/v1/teams', (req, res, ctx) => {
return res(ctx.status(500));
})
);
render(<TeamSwitcher currentTeam={createMockTeam()} />);
await waitFor(() => {
expect(screen.getByText('Error loading teams')).toBeInTheDocument();
});
});
});6. Integration Tests
Testing complete features:
typescript
// test/integration/auth-flow.test.tsx
import { render, screen, fireEvent, waitFor } from '@/test/utils/test-utils';
import { SignInPage } from '@/app/auth/sign-in/page';
describe('Authentication Flow', () => {
it('completes sign in flow', async () => {
render(<SignInPage />);
// Fill form
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' },
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' },
});
// Submit
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(mockRouter.push).toHaveBeenCalledWith('/dashboard');
});
});
it('displays validation errors', async () => {
render(<SignInPage />);
// Submit empty form
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
expect(screen.getByText(/password is required/i)).toBeInTheDocument();
});
});
});7. Playwright E2E Configuration
End-to-end testing setup:
typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
],
webServer: {
command: 'pnpm dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});8. E2E Test Examples
Full user journey tests:
typescript
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('user can sign up, verify email, and sign in', async ({ page }) => {
// Go to sign up
await page.goto('/auth/sign-up');
// Fill sign up form
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'SecurePass123!');
await page.fill('[name="confirmPassword"]', 'SecurePass123!');
// Submit
await page.click('button[type="submit"]');
// Check for verification message
await expect(page.locator('text=Check your email')).toBeVisible();
// Simulate email verification (in real test, check email)
await page.goto('/auth/verify?token=mock-token');
// Should redirect to sign in
await expect(page).toHaveURL('/auth/sign-in');
// Sign in
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'SecurePass123!');
await page.click('button[type="submit"]');
// Should be logged in
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('text=Welcome')).toBeVisible();
});
});
// e2e/subscription.spec.ts
test.describe('Subscription Flow', () => {
test.use({ storageState: 'e2e/.auth/user.json' }); // Authenticated state
test('user can subscribe to pro plan', async ({ page }) => {
await page.goto('/billing');
// Click upgrade on pro plan
await page.click('text=Pro >> ../.. >> button:has-text("Subscribe")');
// Should redirect to Stripe checkout
await page.waitForURL(/checkout\.stripe\.com/);
// Fill test card details
await page.fill('[placeholder="Card number"]', '4242424242424242');
await page.fill('[placeholder="MM / YY"]', '12/25');
await page.fill('[placeholder="CVC"]', '123');
// Complete purchase
await page.click('button:has-text("Subscribe")');
// Should redirect back with success
await page.waitForURL('/billing/success');
await expect(page.locator('text=Subscription activated')).toBeVisible();
});
});9. Test Database Setup
Isolated test database:
typescript
// test/setup/database.ts
import { execSync } from 'child_process';
export async function setupTestDatabase() {
// Reset database
execSync('pnpm supabase db reset', {
env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL }
});
// Seed test data
await seedTestData();
}
export async function cleanupTestDatabase() {
// Clean up after tests
const client = createTestClient();
await client.from('users').delete().neq('id', '00000000-0000-0000-0000-000000000000');
}
// Run before all tests
global.beforeAll(async () => {
await setupTestDatabase();
});
global.afterAll(async () => {
await cleanupTestDatabase();
});10. CI/CD Integration
GitHub Actions workflow:
yaml
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- run: pnpm install
- name: Run unit tests
run: pnpm test:unit
- name: Run integration tests
run: pnpm test:integration
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
- name: Install Playwright
run: pnpm exec playwright install --with-deps
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30Test Scripts
Add to package.json:
json
{
"scripts": {
"test": "jest",
"test:unit": "jest --testPathPattern='.test.ts$'",
"test:integration": "jest --testPathPattern='.integration.ts$'",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}Best Practices
- Test Pyramid: More unit tests, fewer E2E tests
- Isolation: Each test should be independent
- Mocking: Mock external dependencies
- Factories: Use test data factories
- Parallel: Run tests in parallel when possible
License Requirement
Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.
