skills/dexploarer/claudius-skills/testing-framework-helper

testing-framework-helper

Originally fromdexploarer/hyper-forge
SKILL.md

Testing Framework Helper Skill

Expert at creating comprehensive test suites following testing best practices.

When to Activate

  • "write tests for [component/function]"
  • "create test suite for [feature]"
  • "generate unit/integration/E2E tests"

Jest/Vitest (JavaScript/TypeScript)

// UserService.test.ts
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { UserService } from './UserService';
import { mockDatabase } from '../test-utils/mockDatabase';

describe('UserService', () => {
  let userService: UserService;
  let db: ReturnType<typeof mockDatabase>;

  beforeEach(() => {
    db = mockDatabase();
    userService = new UserService(db);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('getById', () => {
    it('should return user when found', async () => {
      const mockUser = {
        id: 1,
        name: 'John Doe',
        email: 'john@example.com',
      };

      db.user.findUnique.mockResolvedValue(mockUser);

      const result = await userService.getById(1);

      expect(result).toEqual(mockUser);
      expect(db.user.findUnique).toHaveBeenCalledWith({
        where: { id: 1 },
      });
    });

    it('should return null when user not found', async () => {
      db.user.findUnique.mockResolvedValue(null);

      const result = await userService.getById(999);

      expect(result).toBeNull();
    });

    it('should throw error on database failure', async () => {
      db.user.findUnique.mockRejectedValue(new Error('DB Error'));

      await expect(userService.getById(1)).rejects.toThrow('DB Error');
    });
  });

  describe('create', () => {
    it('should create user with valid data', async () => {
      const userData = {
        name: 'Jane Doe',
        email: 'jane@example.com',
        password: 'SecurePass123!',
      };

      const createdUser = {
        id: 2,
        ...userData,
        password: 'hashed_password',
      };

      db.user.create.mockResolvedValue(createdUser);

      const result = await userService.create(userData);

      expect(result).toEqual(createdUser);
      expect(db.user.create).toHaveBeenCalledWith({
        data: expect.objectContaining({
          name: userData.name,
          email: userData.email,
        }),
      });
    });

    it('should throw error when email exists', async () => {
      const userData = {
        name: 'John Doe',
        email: 'existing@example.com',
        password: 'pass123',
      };

      db.user.findUnique.mockResolvedValue({ id: 1 });

      await expect(userService.create(userData)).rejects.toThrow(
        'Email already exists'
      );
    });
  });
});

React Testing Library

// UserList.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { UserList } from './UserList';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const mockUsers = [
  { id: 1, name: 'Alice', email: 'alice@test.com' },
  { id: 2, name: 'Bob', email: 'bob@test.com' },
];

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  return ({ children }) => (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
};

describe('UserList', () => {
  beforeEach(() => {
    global.fetch = vi.fn();
  });

  it('renders loading state initially', () => {
    (global.fetch as any).mockImplementation(() => new Promise(() => {}));

    render(<UserList />, { wrapper: createWrapper() });

    expect(screen.getByText(/loading/i)).toBeInTheDocument();
  });

  it('renders user list after loading', async () => {
    (global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => mockUsers,
    });

    render(<UserList />, { wrapper: createWrapper() });

    await waitFor(() => {
      expect(screen.getByText('Alice')).toBeInTheDocument();
      expect(screen.getByText('Bob')).toBeInTheDocument();
    });
  });

  it('handles search functionality', async () => {
    render(<UserList />, { wrapper: createWrapper() });

    const searchInput = screen.getByPlaceholderText(/search/i);
    fireEvent.change(searchInput, { target: { value: 'Alice' } });

    await waitFor(() => {
      expect(global.fetch).toHaveBeenCalledWith(
        expect.stringContaining('search=Alice')
      );
    });
  });

  it('handles error state', async () => {
    (global.fetch as any).mockRejectedValueOnce(new Error('API Error'));

    render(<UserList />, { wrapper: createWrapper() });

    await waitFor(() => {
      expect(screen.getByText(/error/i)).toBeInTheDocument();
    });
  });
});

Pytest (Python)

# test_user_service.py
import pytest
from unittest.mock import Mock, patch, AsyncMock
from app.services.user_service import UserService
from app.models.user import User
from app.exceptions import ValidationError, NotFoundError

@pytest.fixture
def user_service():
    """Fixture for UserService instance"""
    return UserService()

@pytest.fixture
def mock_user():
    """Fixture for mock user data"""
    return User(
        id=1,
        email="test@example.com",
        name="Test User",
        role="user"
    )

class TestUserService:
    @pytest.mark.asyncio
    async def test_get_by_id_success(self, user_service, mock_user):
        """Test getting user by ID successfully"""
        with patch.object(User, 'get', return_value=mock_user) as mock_get:
            result = await user_service.get_by_id(1)

            assert result == mock_user
            mock_get.assert_called_once_with(id=1)

    @pytest.mark.asyncio
    async def test_get_by_id_not_found(self, user_service):
        """Test getting non-existent user"""
        with patch.object(User, 'get', return_value=None):
            with pytest.raises(NotFoundError, match="User not found"):
                await user_service.get_by_id(999)

    @pytest.mark.asyncio
    async def test_create_success(self, user_service):
        """Test creating user successfully"""
        user_data = {
            "email": "new@example.com",
            "name": "New User",
            "password": "SecurePass123!"
        }

        with patch.object(User, 'create', return_value=Mock(id=2)) as mock_create:
            result = await user_service.create(user_data)

            assert result.id == 2
            mock_create.assert_called_once()

    @pytest.mark.asyncio
    async def test_create_duplicate_email(self, user_service, mock_user):
        """Test creating user with existing email"""
        user_data = {
            "email": "existing@example.com",
            "name": "Test",
            "password": "pass123"
        }

        with patch.object(User, 'get_by_email', return_value=mock_user):
            with pytest.raises(ValidationError, match="Email already exists"):
                await user_service.create(user_data)

    @pytest.mark.parametrize("email,valid", [
        ("valid@example.com", True),
        ("invalid-email", False),
        ("", False),
        ("test@test", False),
    ])
    def test_validate_email(self, user_service, email, valid):
        """Test email validation with various inputs"""
        if valid:
            user_service.validate_email(email)
        else:
            with pytest.raises(ValidationError):
                user_service.validate_email(email)

Integration Tests

// user.integration.test.ts
import request from 'supertest';
import { app } from '../app';
import { setupTestDB, cleanupTestDB, seedTestData } from '../test-utils';

describe('User API Integration Tests', () => {
  let authToken: string;

  beforeAll(async () => {
    await setupTestDB();
    const seedData = await seedTestData();
    authToken = seedData.adminToken;
  });

  afterAll(async () => {
    await cleanupTestDB();
  });

  describe('GET /api/users', () => {
    it('should return paginated users', async () => {
      const response = await request(app)
        .get('/api/users')
        .set('Authorization', `Bearer ${authToken}`)
        .query({ page: 1, limit: 10 })
        .expect(200);

      expect(response.body).toHaveProperty('users');
      expect(response.body).toHaveProperty('meta');
      expect(Array.isArray(response.body.users)).toBe(true);
      expect(response.body.meta).toMatchObject({
        page: 1,
        limit: 10,
      });
    });

    it('should require authentication', async () => {
      await request(app)
        .get('/api/users')
        .expect(401);
    });
  });

  describe('POST /api/users', () => {
    it('should create new user with valid data', async () => {
      const userData = {
        name: 'New User',
        email: 'newuser@test.com',
        password: 'SecurePass123!',
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201);

      expect(response.body.data).toMatchObject({
        name: userData.name,
        email: userData.email,
      });
      expect(response.body.data).not.toHaveProperty('password');
    });

    it('should reject invalid email', async () => {
      const userData = {
        name: 'Test',
        email: 'invalid-email',
        password: 'pass123',
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(400);

      expect(response.body).toHaveProperty('error');
    });
  });
});

E2E Tests (Playwright/Cypress)

// e2e/user-management.spec.ts
import { test, expect } from '@playwright/test';

test.describe('User Management', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
    await page.fill('[name="email"]', 'admin@test.com');
    await page.fill('[name="password"]', 'admin123');
    await page.click('button[type="submit"]');
    await expect(page).toHaveURL('/dashboard');
  });

  test('should display user list', async ({ page }) => {
    await page.goto('/users');

    await expect(page.locator('h1')).toContainText('Users');
    await expect(page.locator('[data-testid="user-row"]')).toHaveCount(10);
  });

  test('should create new user', async ({ page }) => {
    await page.goto('/users');
    await page.click('[data-testid="add-user-button"]');

    await page.fill('[name="name"]', 'Test User');
    await page.fill('[name="email"]', 'test@example.com');
    await page.fill('[name="password"]', 'SecurePass123!');

    await page.click('[data-testid="submit-button"]');

    await expect(page.locator('.success-message')).toBeVisible();
    await expect(page.locator('text=Test User')).toBeVisible();
  });

  test('should search users', async ({ page }) => {
    await page.goto('/users');

    await page.fill('[data-testid="search-input"]', 'Alice');
    await page.waitForLoadState('networkidle');

    const rows = page.locator('[data-testid="user-row"]');
    await expect(rows).toHaveCount(1);
    await expect(rows.first()).toContainText('Alice');
  });
});

Best Practices

  • Follow AAA pattern (Arrange, Act, Assert)
  • Use descriptive test names
  • Test edge cases and error conditions
  • Mock external dependencies
  • Use fixtures for test data
  • Clean up after tests
  • Test both happy and unhappy paths
  • Use parameterized tests for similar cases
  • Maintain test independence
  • Keep tests fast
  • Use proper assertion libraries
  • Test accessibility
  • Use test coverage tools
  • Avoid test interdependence

Output Checklist

  • ✅ Test file created
  • ✅ Setup/teardown configured
  • ✅ Unit tests written
  • ✅ Integration tests (if needed)
  • ✅ E2E tests (if needed)
  • ✅ Mocks/fixtures created
  • ✅ Edge cases covered
  • 📝 Test documentation
Weekly Installs
3
GitHub Stars
4
First Seen
9 days ago
Installed on
kiro-cli3
opencode2
gemini-cli2
antigravity2
claude-code2
github-copilot2