Overview
The Tester agent is GemKit's quality assurance specialist, designed to generate comprehensive test suites, analyze coverage gaps, and ensure your code meets high quality standards. Unlike generic AI that might write a few basic tests, the Tester agent understands testing methodologies, knows multiple testing frameworks, and can create unit tests, integration tests, E2E tests, and edge case scenarios that developers often overlook.
Core Capabilities:
- Unit test generation - Vitest, Jest, testing-library
- Integration test scaffolding - API endpoints, database operations
- E2E test scenarios - Playwright, Cypress
- Coverage gap identification - Analyzes and fills missing test cases
- Edge case testing - Error conditions, boundary values, race conditions
When to Use Tester
✅ Ideal Use Cases
Test Generation for Existing Code
- Legacy code without tests
- Newly implemented features
- Refactored components
- Third-party code you've adopted
Improving Test Coverage
- Achieving target coverage goals (80%, 90%, etc.)
- Filling gaps in existing test suites
- Adding edge case tests
- Testing error handling paths
E2E Test Creation
- User flow testing with Playwright
- Critical path verification
- Regression test suites
- Cross-browser compatibility tests
Testing Edge Cases
- Boundary value testing
- Error condition handling
- Race condition scenarios
- Network failure simulation
❌ Not Ideal For
Implementation Work → Use Code-Executor agent
- Writing production code
- Implementing features
- Refactoring logic
Research Tasks → Use Researcher agent
- Finding best testing practices
- Comparing testing frameworks
- Investigating testing strategies
Spawning the Tester Agent
Basic Usage
gk agent spawn --agent tester \
--context src/services/payment.ts \
--prompt "Write comprehensive Vitest tests for this payment service"
What happens:
- Agent analyzes the source code
- Identifies functions, classes, and logic paths
- Determines appropriate test cases
- Generates test file with multiple scenarios
- Includes edge cases and error handling tests
- Provides coverage analysis
Generate Unit Tests
gk agent spawn --agent tester \
--context src/utils/validation.ts \
--prompt "Generate Vitest unit tests covering all validation functions"
gk agent spawn --agent tester \
--context src/services/auth.ts,src/utils/jwt.ts \
--prompt "Write unit tests for authentication service and JWT utilities"
gk agent spawn --agent tester \
--context src/components/Button.tsx \
--prompt "Write Jest tests with React Testing Library for this Button component"
Integration Tests
gk agent spawn --agent tester \
--skills "testing,backend-development" \
--context src/api/users.ts,src/middleware/ \
--prompt "Create integration tests for user API endpoints. Test authentication, validation, and database operations."
gk agent spawn --agent tester \
--skills "testing,backend-development" \
--context src/models/User.ts,src/repositories/userRepository.ts \
--prompt "Write integration tests for User model and repository. Use in-memory SQLite for testing."
E2E Tests with Playwright
gk agent spawn --agent tester \
--skills "testing,frontend-development" \
--context src/pages/checkout/ \
--prompt "Write Playwright E2E tests for the complete checkout flow: cart → shipping → payment → confirmation"
gk agent spawn --agent tester \
--skills "testing" \
--prompt "Create Playwright test suite for user authentication:
- Successful login
- Invalid credentials
- Password reset flow
- Session persistence
- Logout"
Coverage Analysis and Gap Filling
gk agent spawn --agent tester \
--context coverage/lcov-report/index.html,src/services/ \
--prompt "Analyze the coverage report and generate tests for uncovered branches in the services directory"
gk agent spawn --agent tester \
--context src/utils/,tests/utils/ \
--prompt "Current coverage is 67%. Generate additional tests to reach 90% coverage for the utils directory."
Real-World Scenarios
Scenario 1: Achieving 100% Coverage
Context: You have a utility module with partial test coverage.
npm run test:coverage
gk agent spawn --agent tester \
--context src/utils/currency.ts,tests/utils/currency.test.ts \
--prompt "Current coverage is 72%. Analyze uncovered lines (15-18, 34-38, 52) and add tests to reach 100% coverage. Include edge cases."
Expected output:
describe('currency edge cases', () => {
it('should throw error for invalid currency code', () => {
expect(() => formatCurrency(100, 'INVALID')).toThrow('Unknown currency code');
});
it('should format negative amounts correctly', () => {
expect(formatCurrency(-100, 'USD')).toBe('-$100.00');
});
it('should handle very large negative numbers', () => {
expect(formatCurrency(-999999999.99, 'USD')).toBe('-$999,999,999.99');
});
it('should format zero correctly', () => {
expect(formatCurrency(0, 'USD')).toBe('$0.00');
});
it('should handle floating point precision', () => {
expect(formatCurrency(0.1 + 0.2, 'USD')).toBe('$0.30');
});
it('should handle very small fractional amounts', () => {
expect(formatCurrency(0.001, 'USD')).toBe('$0.00');
});
});
Result: Coverage increases from 72% → 100%
Scenario 2: Testing React Components
Context: New React component needs comprehensive tests.
gk agent spawn --agent tester \
--skills "testing,frontend-development" \
--context src/components/UserProfile.tsx,src/hooks/useAuth.ts \
--prompt "Write comprehensive React Testing Library tests for UserProfile component. Include:
- Rendering with different user states
- Loading and error states
- User interaction tests (edit button, save)
- Accessibility tests
- Mock API calls"
Expected output:
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { UserProfile } from '@/components/UserProfile';
import { useAuth } from '@/hooks/useAuth';
vi.mock('@/hooks/useAuth');
describe('UserProfile', () => {
const mockUser = {
id: '1',
name: 'John Doe',
email: 'john@example.com',
avatar: 'https://example.com/avatar.jpg'
};
beforeEach(() => {
vi.mocked(useAuth).mockReturnValue({
user: mockUser,
loading: false,
error: null
});
});
describe('Rendering', () => {
it('should render user information correctly', () => {
render(<UserProfile />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
expect(screen.getByRole('img', { name: /avatar/i })).toHaveAttribute(
'src',
mockUser.avatar
);
});
it('should render loading state', () => {
vi.mocked(useAuth).mockReturnValue({
user: null,
loading: true,
error: null
});
render(<UserProfile />);
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
it('should render error state', () => {
vi.mocked(useAuth).mockReturnValue({
user: null,
loading: false,
error: new Error('Failed to load user')
});
render(<UserProfile />);
expect(screen.getByText(/failed to load user/i)).toBeInTheDocument();
});
});
describe('User Interactions', () => {
it('should open edit mode when edit button is clicked', () => {
render(<UserProfile />);
const editButton = screen.getByRole('button', { name: /edit/i });
fireEvent.click(editButton);
expect(screen.getByRole('textbox', { name: /name/i })).toHaveValue('John Doe');
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
});
it('should save changes when save button is clicked', async () => {
const mockUpdateUser = vi.fn().mockResolvedValue({});
vi.mocked(useAuth).mockReturnValue({
user: mockUser,
loading: false,
error: null,
updateUser: mockUpdateUser
});
render(<UserProfile />);
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
const nameInput = screen.getByRole('textbox', { name: /name/i });
fireEvent.change(nameInput, { target: { value: 'Jane Doe' } });
fireEvent.click(screen.getByRole('button', { name: /save/i }));
await waitFor(() => {
expect(mockUpdateUser).toHaveBeenCalledWith({
...mockUser,
name: 'Jane Doe'
});
});
});
it('should cancel edit mode', () => {
render(<UserProfile />);
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
expect(screen.getByRole('textbox', { name: /name/i })).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
expect(screen.queryByRole('textbox', { name: /name/i })).not.toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have proper ARIA labels', () => {
render(<UserProfile />);
expect(screen.getByRole('img', { name: /avatar/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /edit profile/i })).toBeInTheDocument();
});
it('should be keyboard navigable', () => {
render(<UserProfile />);
const editButton = screen.getByRole('button', { name: /edit/i });
editButton.focus();
expect(document.activeElement).toBe(editButton);
});
});
describe('Edge Cases', () => {
it('should handle missing avatar gracefully', () => {
vi.mocked(useAuth).mockReturnValue({
user: { ...mockUser, avatar: null },
loading: false,
error: null
});
render(<UserProfile />);
expect(screen.getByText('JD')).toBeInTheDocument();
});
it('should handle very long names', () => {
vi.mocked(useAuth).mockReturnValue({
user: { ...mockUser, name: 'A'.repeat(100) },
loading: false,
error: null
});
render(<UserProfile />);
const nameElement = screen.getByText(/A{100}/);
expect(nameElement).toHaveStyle({ textOverflow: 'ellipsis' });
});
});
});
Coverage: Component 96%, hooks tested via integration
Scenario 3: API Contract Testing
Context: REST API endpoints need integration tests.
gk agent spawn --agent tester \
--skills "testing,backend-development" \
--context src/api/products.ts,src/models/Product.ts \
--prompt "Create integration tests for products API using supertest and MSW for database mocking. Test:
- GET /api/products (list with pagination)
- GET /api/products/:id (single product)
- POST /api/products (create)
- PUT /api/products/:id (update)
- DELETE /api/products/:id (delete)
Include authentication, validation errors, and edge cases."
Expected output:
import request from 'supertest';
import { app } from '@/app';
import { db } from '@/config/database';
import { setupTestDatabase, teardownTestDatabase } from '@/test-utils/database';
describe('Products API', () => {
beforeAll(async () => {
await setupTestDatabase();
});
afterAll(async () => {
await teardownTestDatabase();
});
beforeEach(async () => {
await db.products.deleteMany();
});
describe('GET /api/products', () => {
it('should return empty array when no products exist', async () => {
const response = await request(app)
.get('/api/products')
.expect(200);
expect(response.body).toEqual({
data: [],
pagination: { page: 1, limit: 10, total: 0 }
});
});
it('should return paginated products', async () => {
await db.products.createMany({
data: Array.from({ length: 25 }, (_, i) => ({
name: `Product ${i + 1}`,
price: 100 + i
}))
});
const response = await request(app)
.get('/api/products?page=2&limit=10')
.expect(200);
expect(response.body.data).toHaveLength(10);
expect(response.body.pagination).toEqual({
page: 2,
limit: 10,
total: 25
});
});
it('should filter products by price range', async () => {
await db.products.createMany({
data: [
{ name: 'Cheap', price: 10 },
{ name: 'Medium', price: 50 },
{ name: 'Expensive', price: 100 }
]
});
const response = await request(app)
.get('/api/products?minPrice=20&maxPrice=80')
.expect(200);
expect(response.body.data).toHaveLength(1);
expect(response.body.data[0].name).toBe('Medium');
});
});
describe('GET /api/products/:id', () => {
it('should return product by ID', async () => {
const product = await db.products.create({
data: { name: 'Test Product', price: 99.99 }
});
const response = await request(app)
.get(`/api/products/${product.id}`)
.expect(200);
expect(response.body).toMatchObject({
id: product.id,
name: 'Test Product',
price: 99.99
});
});
it('should return 404 for non-existent product', async () => {
await request(app)
.get('/api/products/non-existent-id')
.expect(404);
});
});
describe('POST /api/products', () => {
it('should create new product with valid data', async () => {
const newProduct = {
name: 'New Product',
price: 49.99,
description: 'A test product'
};
const response = await request(app)
.post('/api/products')
.set('Authorization', 'Bearer valid-token')
.send(newProduct)
.expect(201);
expect(response.body).toMatchObject(newProduct);
expect(response.body.id).toBeDefined();
});
it('should require authentication', async () => {
await request(app)
.post('/api/products')
.send({ name: 'Product', price: 10 })
.expect(401);
});
it('should validate required fields', async () => {
const response = await request(app)
.post('/api/products')
.set('Authorization', 'Bearer valid-token')
.send({ price: 10 })
.expect(400);
expect(response.body.errors).toContainEqual(
expect.objectContaining({ field: 'name' })
);
});
it('should validate price is positive', async () => {
const response = await request(app)
.post('/api/products')
.set('Authorization', 'Bearer valid-token')
.send({ name: 'Product', price: -10 })
.expect(400);
expect(response.body.errors).toContainEqual(
expect.objectContaining({
field: 'price',
message: expect.stringContaining('positive')
})
);
});
});
describe('PUT /api/products/:id', () => {
it('should update existing product', async () => {
const product = await db.products.create({
data: { name: 'Original', price: 100 }
});
const response = await request(app)
.put(`/api/products/${product.id}`)
.set('Authorization', 'Bearer valid-token')
.send({ name: 'Updated', price: 150 })
.expect(200);
expect(response.body).toMatchObject({
id: product.id,
name: 'Updated',
price: 150
});
});
it('should return 404 for non-existent product', async () => {
await request(app)
.put('/api/products/non-existent-id')
.set('Authorization', 'Bearer valid-token')
.send({ name: 'Updated' })
.expect(404);
});
});
describe('DELETE /api/products/:id', () => {
it('should delete existing product', async () => {
const product = await db.products.create({
data: { name: 'To Delete', price: 100 }
});
await request(app)
.delete(`/api/products/${product.id}`)
.set('Authorization', 'Bearer valid-token')
.expect(204);
const deleted = await db.products.findUnique({
where: { id: product.id }
});
expect(deleted).toBeNull();
});
it('should require authentication', async () => {
await request(app)
.delete('/api/products/some-id')
.expect(401);
});
});
});
Coverage: API endpoints 100%, authentication middleware tested
Scenario 4: E2E User Flow Testing
Context: Critical checkout flow needs E2E tests.
gk agent spawn --agent tester \
--skills "testing,frontend-development" \
--prompt "Write Playwright E2E tests for complete e-commerce checkout flow:
1. Browse products
2. Add items to cart
3. Proceed to checkout
4. Fill shipping information
5. Enter payment details
6. Place order
7. Verify order confirmation
Include error scenarios like payment failure."
Expected output:
import { test, expect } from '@playwright/test';
test.describe('Checkout Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('complete successful checkout', async ({ page }) => {
await page.goto('/products');
await expect(page.locator('h1')).toContainText('Products');
await page.locator('[data-testid="product-1"]').click();
await page.locator('button', { hasText: 'Add to Cart' }).click();
await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
await page.goto('/products');
await page.locator('[data-testid="product-2"]').click();
await page.locator('button', { hasText: 'Add to Cart' }).click();
await expect(page.locator('[data-testid="cart-count"]')).toHaveText('2');
await page.locator('[data-testid="cart-icon"]').click();
await page.locator('button', { hasText: 'Checkout' }).click();
await expect(page).toHaveURL(/.*\/checkout\/shipping/);
await page.fill('[name="firstName"]', 'John');
await page.fill('[name="lastName"]', 'Doe');
await page.fill('[name="address"]', '123 Main St');
await page.fill('[name="city"]', 'San Francisco');
await page.fill('[name="zipCode"]', '94102');
await page.selectOption('[name="country"]', 'US');
await page.click('button', { hasText: 'Continue to Payment' });
await expect(page).toHaveURL(/.*\/checkout\/payment/);
await page.fill('[name="cardNumber"]', '4242424242424242');
await page.fill('[name="cardExpiry"]', '12/25');
await page.fill('[name="cardCvc"]', '123');
await page.fill('[name="cardName"]', 'John Doe');
await page.click('button', { hasText: 'Place Order' });
await expect(page).toHaveURL(/.*\/order\/confirmation/);
await expect(page.locator('h1')).toContainText('Order Confirmed');
await expect(page.locator('[data-testid="order-number"]')).toBeVisible();
await expect(page.locator('[data-testid="order-items"]')).toContainText('Product 1');
await expect(page.locator('[data-testid="order-items"]')).toContainText('Product 2');
await expect(page.locator('[data-testid="shipping-address"]')).toContainText('123 Main St');
});
test('handle payment failure', async ({ page }) => {
await page.goto('/products');
await page.locator('[data-testid="product-1"]').click();
await page.locator('button', { hasText: 'Add to Cart' }).click();
await page.locator('[data-testid="cart-icon"]').click();
await page.locator('button', { hasText: 'Checkout' }).click();
await page.fill('[name="firstName"]', 'John');
await page.fill('[name="lastName"]', 'Doe');
await page.fill('[name="address"]', '123 Main St');
await page.fill('[name="city"]', 'San Francisco');
await page.fill('[name="zipCode"]', '94102');
await page.selectOption('[name="country"]', 'US');
await page.click('button', { hasText: 'Continue to Payment' });
await page.fill('[name="cardNumber"]', '4000000000000002');
await page.fill('[name="cardExpiry"]', '12/25');
await page.fill('[name="cardCvc"]', '123');
await page.fill('[name="cardName"]', 'John Doe');
await page.click('button', { hasText: 'Place Order' });
await expect(page.locator('[role="alert"]')).toContainText('Payment failed');
await expect(page).toHaveURL(/.*\/checkout\/payment/);
await expect(page.locator('button', { hasText: 'Place Order' })).toBeEnabled();
});
test('validate shipping form', async ({ page }) => {
await page.goto('/products');
await page.locator('[data-testid="product-1"]').click();
await page.locator('button', { hasText: 'Add to Cart' }).click();
await page.locator('[data-testid="cart-icon"]').click();
await page.locator('button', { hasText: 'Checkout' }).click();
await page.click('button', { hasText: 'Continue to Payment' });
await expect(page.locator('[data-error="firstName"]')).toBeVisible();
await expect(page.locator('[data-error="address"]')).toBeVisible();
await expect(page).toHaveURL(/.*\/checkout\/shipping/);
});
test('maintain cart contents across page refresh', async ({ page }) => {
await page.goto('/products');
await page.locator('[data-testid="product-1"]').click();
await page.locator('button', { hasText: 'Add to Cart' }).click();
await page.reload();
await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
});
});
Coverage: Complete user journey, error scenarios, validation
Testing Skills Reference
The Tester agent leverages these skills for domain-specific testing knowledge:
testing skill - General testing methodology
- Test structure and organization
- Mocking and stubbing patterns
- Coverage analysis
- Edge case identification
vitest skill (if available) - Vitest-specific patterns
- Vitest configuration
- vi.mock() usage
- Snapshot testing
- Performance testing
jest skill (if available) - Jest-specific patterns
- Jest matchers
- jest.mock() patterns
- Timer mocking
- Module mocking
playwright skill (if available) - E2E testing
- Page object model
- Locator strategies
- Visual regression testing
- Network interception
Best Practices
1. Provide the Source Code as Context
gk agent spawn --agent tester --prompt "Write tests for auth service"
gk agent spawn --agent tester \
--context src/services/auth.ts \
--prompt "Write tests for auth service"
2. Specify Testing Framework Preference
gk agent spawn --agent tester \
--context src/utils/currency.ts \
--prompt "Write Vitest tests using @testing-library/react"
3. Ask for Edge Cases Explicitly
gk agent spawn --agent tester \
--context src/services/payment.ts \
--prompt "Write tests including edge cases:
- Network failures
- Invalid card numbers
- Duplicate transactions
- Race conditions"
4. Request Coverage Report Analysis
gk agent spawn --agent tester \
--context src/utils/,coverage/lcov.info \
--prompt "Analyze coverage report and add tests for uncovered branches"
5. Combine with Code-Executor for TDD
gk agent spawn --agent tester \
--prompt "Write Vitest tests for a currency converter function supporting USD, EUR, GBP"
gk agent spawn --agent code-executor \
--context tests/utils/currency.test.ts \
--prompt "Implement currency converter to pass these tests"
Summary
The Tester agent is your quality assurance partner, specializing in comprehensive test generation across unit, integration, and E2E testing. By understanding testing frameworks, methodologies, and edge cases, it helps you achieve high coverage and confidence in your code quality. Use it to fill testing gaps, achieve coverage goals, and ensure robust error handling.
Key takeaways:
- Always provide source code as context for accurate tests
- Specify testing framework and tools explicitly
- Request edge cases and error scenarios
- Use coverage reports to identify gaps
- Combine with Code-Executor for test-driven development
- Review generated tests to ensure they match your standards