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
-
Analyze Target Code
-
Read file with Read tool
-
Identify testable units:
- Functions/methods
- Components
- API endpoints
- Data transformations
-
List dependencies to mock
-
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
- Write Tests
For NEW code (TDD - Phase 5a):
- Write failing tests (RED) → Tests MUST fail → If they pass, tests are wrong
- Implement code (GREEN) → Minimal code to pass
- Refactor (REFACTOR) → Tests must stay green
For EXISTING code:
-
Write tests that pass (validate current behavior)
-
Add edge case tests
-
Add negative tests (error handling)
-
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