testing
Installation
SKILL.md
Testing Skill
Test-driven development and comprehensive testing strategies for frontend and backend code.
When to Use This Skill
- Writing unit tests for new features
- Creating E2E tests with Playwright
- Testing Go handlers and services
- Debugging test failures
- Improving test coverage
๐งช Test-Driven Development (TDD)
The TDD Cycle
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. RED: Write failing test โ
โ โ โ
โ 2. GREEN: Write minimal code โ
โ โ โ
โ 3. REFACTOR: Clean up code โ
โ โ โ
โ (Repeat) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TDD Benefits
- Forces you to think about design first
- Produces testable, modular code
- Provides instant regression safety
- Documents expected behavior
๐ฏ Frontend Testing
Unit Testing (Vitest + Testing Library)
Setup
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
Configuration (vitest.config.ts)
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
globals: true,
},
});
Component Test Example
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('submits form with valid credentials', async () => {
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
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: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
it('shows validation error for invalid email', async () => {
render(<LoginForm onSubmit={vi.fn()} />);
await userEvent.type(screen.getByLabelText(/email/i), 'invalid-email');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
it('disables submit button while loading', () => {
render(<LoginForm onSubmit={vi.fn()} loading />);
expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled();
});
});
Hook Testing
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
๐ญ E2E Testing (Playwright)
Setup
npm init playwright@latest
Configuration (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: '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'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
Page Object Model
// e2e/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
E2E Test Example
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
test.describe('Authentication', () => {
test('successful login redirects to dashboard', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'validpassword');
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome')).toBeVisible();
});
test('invalid credentials show error', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'wrongpassword');
await expect(loginPage.errorMessage).toHaveText('Invalid credentials');
await expect(page).toHaveURL('/login');
});
});
API Testing with Playwright
import { test, expect } from '@playwright/test';
test.describe('API Tests', () => {
test('GET /api/users returns user list', async ({ request }) => {
const response = await request.get('/api/users');
expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data.users).toBeInstanceOf(Array);
});
test('POST /api/users creates user', async ({ request }) => {
const response = await request.post('/api/users', {
data: { name: 'Test User', email: 'test@example.com' },
});
expect(response.status()).toBe(201);
const user = await response.json();
expect(user.id).toBeDefined();
});
});
๐ง Go Testing
Table-Driven Tests
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{
name: "valid config",
input: `{"port": 8080}`,
want: &Config{Port: 8080},
},
{
name: "invalid json",
input: `{invalid}`,
wantErr: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
HTTP Handler Testing
func TestHandler_GetUser(t *testing.T) {
// Setup
mockStore := &MockUserStore{
users: map[string]*User{
"123": {ID: "123", Name: "Test User"},
},
}
handler := NewHandler(mockStore)
// Create request
req := httptest.NewRequest("GET", "/users/123", nil)
req = mux.SetURLVars(req, map[string]string{"id": "123"})
w := httptest.NewRecorder()
// Execute
handler.GetUser(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
var response User
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "Test User", response.Name)
}
Mocking with Interfaces
// Define interface
type UserStore interface {
GetUser(id string) (*User, error)
CreateUser(user *User) error
}
// Mock implementation
type MockUserStore struct {
users map[string]*User
err error
}
func (m *MockUserStore) GetUser(id string) (*User, error) {
if m.err != nil {
return nil, m.err
}
user, ok := m.users[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}
Integration Tests
//go:build integration
func TestDatabase_Integration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
db := setupTestDB(t)
defer db.Close()
// Test actual database operations
user := &User{Name: "Test"}
err := db.CreateUser(user)
assert.NoError(t, err)
assert.NotEmpty(t, user.ID)
}
โ Testing Anti-Patterns
Avoid These
- Testing implementation, not behavior
// โ Bad: Testing internal state
assert.Equal(t, 3, len(cache.items))
// โ
Good: Testing behavior
result, err := cache.Get("key")
assert.NoError(t, err)
assert.Equal(t, expectedValue, result)
- Flaky tests
// โ Bad: Time-dependent
expect(Date.now() - startTime).toBeLessThan(100);
// โ
Good: Mock time
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-01-01'));
- Tests that don't clean up
// โ
Good: Always cleanup
func TestWithTempFile(t *testing.T) {
f, err := os.CreateTemp("", "test")
require.NoError(t, err)
defer os.Remove(f.Name()) // Cleanup
// Test logic
}
๐ References
Weekly Installs
1
Repository
aiyuekuang/llmproxyGitHub Stars
11
First Seen
Mar 3, 2026
Security Audits
Installed on
mcpjam1
claude-code1
replit1
junie1
windsurf1
zencoder1