Write tests with TDD. Supports Jest, Cypress, Detox, PHPUnit, PyTest, Go testing.
/plugin marketplace add nguyenthienthanh/aura-frog/plugin install aura-frog@aurafrogThis skill is limited to using the following tools:
Priority: MEDIUM - Use for test-related requests Version: 1.0.0
USE for:
DON'T use for:
bugfix-quickworkflow-orchestrator1. Read file with Read tool
2. Identify testable units:
- Functions/methods
- Components
- API endpoints
- Data transformations
3. List dependencies to mock
| 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 |
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)
# 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
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();
});
});
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);
});
});
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();
});
});
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']);
}
}
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
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)
}
})
}
}
| 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) |
| 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/ |
# 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:
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.