test-writer
Write tests with TDD. Supports Jest, Cypress, Detox, PHPUnit, PyTest, Go testing.
Installation
Suivez les instructions du dépôt pour votre agent. Consultez les fichiers et dépendances avant l’installation.
Voir la source ↗GuidesInstructions
Aura Frog Test Writer
Priority: MEDIUM - Use for test-related requests Version: 1.0.0
When to Use
USE for:
- Adding tests to existing code
- Improving test coverage
- Creating test suites
- TDD implementation (Phase 5a)
- Writing specific test types (unit, integration, E2E)
DON'T use for:
- Bug fixes without explicit test request → use
bugfix-quick - Full feature implementation → use
workflow-orchestrator
Test Writing Process
1. Analyze Target Code
1. Read file with Read tool
2. Identify testable units:
- Functions/methods
- Components
- API endpoints
- Data transformations
3. List dependencies to mock
2. Plan Strategy
| Type | Use For | Scope |
|---|---|---|
| Unit | Individual functions/components | Single unit, mocked deps |
| Integration | Module interactions, API calls | Multiple units together |
| E2E | Complete user flows | Full system, real deps |
3. Write Tests
For NEW code (TDD - Phase 5a):
1. Write failing tests (RED)
→ Tests MUST fail
→ If they pass, tests are wrong
2. Implement code (GREEN)
→ Minimal code to pass
3. Refactor (REFACTOR)
→ Tests must stay green
For EXISTING code:
1. Write tests that pass (validate current behavior)
2. Add edge case tests
3. Add negative tests (error handling)
4. Verify Coverage
# Check target: 80% or project-specific
npm test -- --coverage # JavaScript/TypeScript
pytest --cov=. --cov-report=html # Python
./vendor/bin/phpunit --coverage-html coverage # PHP
go test -coverprofile=coverage.out ./... # Go
Framework-Specific Templates
JavaScript/TypeScript - Jest
Unit Test (Function):
describe('calculateDiscount', () => {
it('should apply 10% discount for orders over $100', () => {
expect(calculateDiscount(150)).toBe(135);
});
it('should return original price for orders under $100', () => {
expect(calculateDiscount(50)).toBe(50);
});
it('should throw error for negative amounts', () => {
expect(() => calculateDiscount(-10)).toThrow('Invalid amount');
});
});
Unit Test (React Component):
import { render, fireEvent, screen } from '@testing-library/react';
import { LoginButton } from './LoginButton';
describe('LoginButton', () => {
it('should call onLogin when clicked', () => {
const onLogin = jest.fn();
render(<LoginButton onLogin={onLogin} />);
fireEvent.click(screen.getByRole('button', { name: /login/i }));
expect(onLogin).toHaveBeenCalledTimes(1);
});
it('should show loading state', () => {
render(<LoginButton isLoading={true} />);
expect(screen.getByTestId('loading-spinner')).toBeVisible();
});
it('should be disabled when loading', () => {
render(<LoginButton isLoading={true} />);
expect(screen.getByRole('button')).toBeDisabled();
});
});
React Native - Jest + React Native Testing Library
Component Test:
import { render, fireEvent } from '@testing-library/react-native';
import { PaymentCard } from './PaymentCard';
describe('PaymentCard', () => {
it('should display amount in correct format', () => {
const { getByTestId } = render(<PaymentCard amount={1500.50} />);
expect(getByTestId('amount-display')).toHaveTextContent('$1,500.50');
});
it('should call onPay when pay button pressed', () => {
const onPay = jest.fn();
const { getByTestId } = render(<PaymentCard onPay={onPay} />);
fireEvent.press(getByTestId('pay-button'));
expect(onPay).toHaveBeenCalledTimes(1);
});
});
React Native - Detox E2E
E2E Test:
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('should login successfully with valid credentials', async () => {
await element(by.id('email-input')).typeText('user@example.com');
await element(by.id('password-input')).typeText('securePassword123');
await element(by.id('login-button')).tap();
await expect(element(by.id('home-screen'))).toBeVisible();
await expect(element(by.text('Welcome'))).toBeVisible();
});
it('should show error for invalid credentials', async () => {
await element(by.id('email-input')).typeText('wrong@email.com');
await element(by.id('password-input')).typeText('wrongpass');
await element(by.id('login-button')).tap();
await expect(element(by.text('Invalid credentials'))).toBeVisible();
});
});
PHP - PHPUnit
Unit Test:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Services\OrderCalculator;
class OrderCalculatorTest extends TestCase
{
private OrderCalculator $calculator;
protected function setUp(): void
{
$this->calculator = new OrderCalculator();
}
public function test_calculates_subtotal_correctly(): void
{
$items = [
['price' => 100, 'quantity' => 2],
['price' => 50, 'quantity' => 1],
];
$result = $this->calculator->calculateSubtotal($items);
$this->assertEquals(250, $result);
}
public function test_applies_discount_percentage(): void
{
$result = $this->calculator->applyDiscount(100, 10);
$this->assertEquals(90, $result);
}
public function test_throws_exception_for_negative_discount(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Discount cannot be negative');
$this->calculator->applyDiscount(100, -5);
}
}
Laravel Feature Test:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class AuthenticationTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_login_with_correct_credentials(): void
{
$user = User::factory()->create([
'email' => 'test@example.com',
'password' => bcrypt('password123'),
]);
$response = $this->postJson('/api/login', [
'email' => 'test@example.com',
'password' => 'password123',
]);
$response->assertStatus(200)
->assertJsonStructure(['token', 'user']);
}
public function test_user_cannot_login_with_wrong_password(): void
{
$user = User::factory()->create([
'email' => 'test@example.com',
'password' => bcrypt('password123'),
]);
$response = $this->postJson('/api/login', [
'email' => 'test@example.com',
'password' => 'wrongpassword',
]);
$response->assertStatus(401)
->assertJson(['message' => 'Invalid credentials']);
}
}
Python - PyTest
Unit Test:
import pytest
from app.services.calculator import OrderCalculator
class TestOrderCalculator:
@pytest.fixture
def calculator(self):
return OrderCalculator()
def test_calculate_subtotal(self, calculator):
items = [
{"price": 100, "quantity": 2},
{"price": 50, "quantity": 1},
]
result = calculator.calculate_subtotal(items)
assert result == 250
def test_apply_discount_percentage(self, calculator):
result = calculator.apply_discount(100, 10)
assert result == 90
def test_raises_error_for_negative_discount(self, calculator):
with pytest.raises(ValueError, match="Discount cannot be negative"):
calculator.apply_discount(100, -5)
@pytest.mark.parametrize("amount,discount,expected", [
(100, 0, 100),
(100, 10, 90),
(100, 50, 50),
(100, 100, 0),
])
def test_discount_edge_cases(self, calculator, amount, discount, expected):
assert calculator.apply_discount(amount, discount) == expected
FastAPI Integration Test:
import pytest
from fastapi.testclient import TestClient
from app.main import app
from app.models import User
@pytest.fixture
def client():
return TestClient(app)
@pytest.fixture
def test_user(db_session):
user = User(email="test@example.com", password="hashed_password")
db_session.add(user)
db_session.commit()
return user
class TestAuthenticationAPI:
def test_login_success(self, client, test_user):
response = client.post("/api/login", json={
"email": "test@example.com",
"password": "password123"
})
assert response.status_code == 200
assert "token" in response.json()
assert "user" in response.json()
def test_login_wrong_password(self, client, test_user):
response = client.post("/api/login", json={
"email": "test@example.com",
"password": "wrongpassword"
})
assert response.status_code == 401
assert response.json()["detail"] == "Invalid credentials"
def test_login_missing_fields(self, client):
response = client.post("/api/login", json={})
assert response.status_code == 422
Go - Go Testing
Unit Test:
package calculator
import (
"testing"
)
func TestCalculateSubtotal(t *testing.T) {
calc := NewOrderCalculator()
items := []Item{
{Price: 100, Quantity: 2},
{Price: 50, Quantity: 1},
}
result := calc.CalculateSubtotal(items)
if result != 250 {
t.Errorf("Expected 250, got %d", result)
}
}
func TestApplyDiscount(t *testing.T) {
calc := NewOrderCalculator()
result := calc.ApplyDiscount(100, 10)
if result != 90 {
t.Errorf("Expected 90, got %d", result)
}
}
func TestApplyDiscountNegative(t *testing.T) {
calc := NewOrderCalculator()
defer func() {
if r := recover(); r == nil {
t.Errorf("Expected panic for negative discount")
}
}()
calc.ApplyDiscount(100, -5)
}
// Table-driven test
func TestApplyDiscountEdgeCases(t *testing.T) {
calc := NewOrderCalculator()
tests := []struct {
name string
amount int
discount int
expected int
}{
{"no discount", 100, 0, 100},
{"10% discount", 100, 10, 90},
{"50% discount", 100, 50, 50},
{"full discount", 100, 100, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := calc.ApplyDiscount(tt.amount, tt.discount)
if result != tt.expected {
t.Errorf("Expected %d, got %d", tt.expected, result)
}
})
}
}
Coverage Targets
| Type | Target | Rationale |
|---|---|---|
| Critical paths | 100% | Auth, payment, security |
| Business logic | 90% | Core domain logic |
| UI/Utilities | 80% | User-facing components |
| Overall | 80% | Project minimum (or custom) |
Test File Naming Conventions
| Framework | Test File | Location |
|---|---|---|
| Jest | *.test.ts, *.spec.ts |
__tests__/ or alongside |
| PHPUnit | *Test.php |
tests/Unit/, tests/Feature/ |
| PyTest | test_*.py, *_test.py |
tests/ |
| Go | *_test.go |
Same package |
| Detox | *.e2e.ts |
e2e/ |
| Cypress | *.cy.ts |
cypress/e2e/ |
Running Tests by Framework
# JavaScript/TypeScript (Jest)
npm test
npm test -- --coverage
npm test -- --watch
# PHP (PHPUnit)
./vendor/bin/phpunit
./vendor/bin/phpunit --coverage-html coverage
./vendor/bin/phpunit --filter TestClassName
# Python (PyTest)
pytest
pytest --cov=. --cov-report=html
pytest -k "test_function_name"
# Go
go test ./...
go test -v ./...
go test -coverprofile=coverage.out ./...
# React Native (Detox)
detox test --configuration ios.sim.debug
detox test --configuration android.emu.debug
Remember:
- Tests are documentation - write clear, maintainable tests
- Follow AAA pattern: Arrange, Act, Assert
- One assertion concept per test (can have multiple expects)
- Test behavior, not implementation
- Mock external dependencies
Exemples
Consultez les exemples du dépôt source.
Voir la source ↗Historique source
- Date d’indexation
- 2026-01-20
- Mise à jour de l’index
- 2026-01-26
- Branch
- main
Les dates concernent ce répertoire. Consultez le dépôt pour l’historique du code.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!