tdd-workflow — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited tdd-workflow (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill ensures all code development follows TDD principles with comprehensive test coverage.
#### 1. Tests BEFORE Code ALWAYS write tests first, then implement code to make tests pass.
#### 2. Coverage Requirements
#### 3. Test Types
##### Unit Tests
##### Integration Tests
##### E2E Tests (Playwright)
#### Step 1: Write User Journeys
As a [role], I want to [action], so that [benefit]
Example:
As a user, I want to search for markets semantically,
so that I can find relevant markets even without exact keywords.#### Step 2: Generate Test Cases For each user journey, create comprehensive test cases:
describe('Semantic Search', () => {
it('returns relevant markets for query', async () => {
// Test implementation
})
it('handles empty query gracefully', async () => {
// Test edge case
})
it('falls back to substring search when Redis unavailable', async () => {
// Test fallback behavior
})
it('sorts results by similarity score', async () => {
// Test sorting logic
})
})#### Step 3: Run Tests (They Should Fail)
npm test
# Tests should fail - we haven't implemented yetThis step is mandatory and is the RED gate for all production changes.
Before modifying business logic or other production code, you must verify a valid RED state via one of these paths:
implementation
regressions
A test that was only written but not compiled and executed does not count as RED.
Do not edit production code until this RED state is confirmed.
#### Step 4: Implement Code Write minimal code to make tests pass:
// Implementation guided by tests
export async function searchMarkets(query: string) {
// Implementation here
}#### Step 5: Run Tests Again
npm test
# Tests should now passRerun the same relevant test target after the fix and confirm the previously failing test is now GREEN.
Only after a valid GREEN result may you proceed to refactor.
#### Step 6: Refactor Improve code quality while keeping tests green:
#### Step 7: Verify Coverage
npm run test:coverage
# Verify ~90% line coverage of real logic achieved#### Unit Test Pattern (Jest/Vitest)
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'
describe('Button Component', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click</Button>)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
})#### API Integration Test Pattern
import { NextRequest } from 'next/server'
import { GET } from './route'
describe('GET /api/markets', () => {
it('returns markets successfully', async () => {
const request = new NextRequest('http://localhost/api/markets')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(Array.isArray(data.data)).toBe(true)
})
it('validates query parameters', async () => {
const request = new NextRequest('http://localhost/api/markets?limit=invalid')
const response = await GET(request)
expect(response.status).toBe(400)
})
it('handles database errors gracefully', async () => {
// Mock database failure
const request = new NextRequest('http://localhost/api/markets')
// Test error handling
})
})#### E2E Test Pattern (Playwright)
import { test, expect } from '@playwright/test'
test('user can search and filter markets', async ({ page }) => {
// Navigate to markets page
await page.goto('/')
await page.click('a[href="/markets"]')
// Verify page loaded
await expect(page.locator('h1')).toContainText('Markets')
// Search for markets
await page.fill('input[placeholder="Search markets"]', 'election')
// Wait for debounce and results
await page.waitForTimeout(600)
// Verify search results displayed
const results = page.locator('[data-testid="market-card"]')
await expect(results).toHaveCount(5, { timeout: 5000 })
// Verify results contain search term
const firstResult = results.first()
await expect(firstResult).toContainText('election', { ignoreCase: true })
// Filter by status
await page.click('button:has-text("Active")')
// Verify filtered results
await expect(results).toHaveCount(3)
})
test('user can create a new market', async ({ page }) => {
// Login first
await page.goto('/creator-dashboard')
// Fill market creation form
await page.fill('input[name="name"]', 'Test Market')
await page.fill('textarea[name="description"]', 'Test description')
await page.fill('input[name="endDate"]', '2025-12-31')
// Submit form
await page.click('button[type="submit"]')
// Verify success message
await expect(page.locator('text=Market created successfully')).toBeVisible()
// Verify redirect to market page
await expect(page).toHaveURL(/\/markets\/test-market/)
})src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx # Unit tests
│ │ └── Button.stories.tsx # Storybook
│ └── MarketCard/
│ ├── MarketCard.tsx
│ └── MarketCard.test.tsx
├── app/
│ └── api/
│ └── markets/
│ ├── route.ts
│ └── route.test.ts # Integration tests
└── e2e/
├── markets.spec.ts # E2E tests
├── trading.spec.ts
└── auth.spec.tsThe mocks below are for fast unit tests only. Do not mock the database when you can exercise it: integration tests must use Testcontainers or a real engine, as mandated in the "Testcontainers Mandate" and "Do not mock what you do not own" sections later in this skill. The Supabase and Redis mocks here stand in for an external boundary in a unit test; they are not a substitute for exercising the real datastore in integration tests.
#### Supabase Mock (unit tests only)
jest.mock('@/lib/supabase', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn(() => ({
eq: jest.fn(() => Promise.resolve({
data: [{ id: 1, name: 'Test Market' }],
error: null
}))
}))
}))
}
}))#### Redis Mock
jest.mock('@/lib/redis', () => ({
searchMarketsByVector: jest.fn(() => Promise.resolve([
{ slug: 'test-market', similarity_score: 0.95 }
])),
checkRedisHealth: jest.fn(() => Promise.resolve({ connected: true }))
}))#### OpenAI Mock
jest.mock('@/lib/openai', () => ({
generateEmbedding: jest.fn(() => Promise.resolve(
new Array(1536).fill(0.1) // Mock 1536-dim embedding
))
}))#### Run Coverage Report
npm run test:coverage#### Coverage Thresholds
{
"jest": {
"coverageThresholds": {
"global": {
"branches": 70,
"functions": 90,
"lines": 90,
"statements": 90
}
}
}
}#### FAIL: WRONG: Testing Implementation Details
// Don't test internal state
expect(component.state.count).toBe(5)#### PASS: CORRECT: Test User-Visible Behavior
// Test what users see
expect(screen.getByText('Count: 5')).toBeInTheDocument()#### FAIL: WRONG: Brittle Selectors
// Breaks easily
await page.click('.css-class-xyz')#### PASS: CORRECT: Semantic Selectors
// Resilient to changes
await page.click('button:has-text("Submit")')
await page.click('[data-testid="submit-button"]')#### FAIL: WRONG: No Test Isolation
// Tests depend on each other
test('creates user', () => { /* ... */ })
test('updates same user', () => { /* depends on previous test */ })#### PASS: CORRECT: Independent Tests
// Each test sets up its own data
test('creates user', () => {
const user = createTestUser()
// Test logic
})
test('updates user', () => {
const user = createTestUser()
// Update logic
})#### Watch Mode During Development
npm test -- --watch
# Tests run automatically on file changes#### Pre-Commit Hook
# Runs before every commit
npm test && npm run lint#### CI/CD Integration
# GitHub Actions
- name: Run Tests
run: npm test -- --coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3// Given, // When, and // Thencomments (Given sets up state, When runs the single action, Then asserts the observable outcome)
Remember: Tests are not optional. They are the safety net that enables confident refactoring, rapid development, and production reliability.
#### Test Naming Convention
Use the <methodName>_<scenario>_<expectedResult> pattern:
// Java (JUnit 5)
@Test
void calculateTotal_withEmptyCart_returnsZero() { }
@Test
void processPayment_whenCardDeclined_throwsPaymentException() { }// TypeScript (Vitest)
test('calculateTotal_withEmptyCart_returnsZero', () => { })
test('processPayment_whenCardDeclined_throwsPaymentException', () => { })Never use vague names like test1, works, or happyPath.
#### TestDataFactory Pattern
Never repeat object construction inline across tests. Extract to a shared factory:
// Java
public class OrderTestFactory {
public static Order validOrder() {
return Order.builder()
.id(UUID.randomUUID())
.customerId("cust-001")
.items(List.of(OrderItem.of("SKU-1", 2, BigDecimal.valueOf(9.99))))
.status(OrderStatus.PENDING)
.build();
}
public static Order cancelledOrder() {
return validOrder().toBuilder().status(OrderStatus.CANCELLED).build();
}
}// TypeScript
export const OrderFactory = {
valid: (): Order => ({
id: crypto.randomUUID(),
customerId: 'cust-001',
items: [{ sku: 'SKU-1', qty: 2, price: 9.99 }],
status: 'pending',
}),
cancelled: (): Order => ({ ...OrderFactory.valid(), status: 'cancelled' }),
}#### Test Pyramid
Maintain these proportions across the test suite:
| Layer | Target | Tools |
|---|---|---|
| Unit | ~70% | JUnit/Vitest, fast, no I/O |
| Integration | ~20% | Testcontainers, real DB/queue |
| Contract | ~5% | Pact, Spring Cloud Contract |
| E2E | ~5% | Playwright, Cypress, Selenium |
#### Contract Testing (Required for External HTTP APIs)
Every HTTP API consumed by an external service must have consumer-driven contract tests:
// Spring Cloud Contract (provider side)
Contract.make {
request {
method 'GET'
url '/api/users/123'
}
response {
status 200
body([id: '123', name: 'Alice'])
headers { contentType(applicationJson()) }
}
}Use Pact for polyglot environments; Spring Cloud Contract for Java-to-Java service contracts.
#### Mutation Testing
Run mutation testing on all critical business logic:
pitest): minimum 70% mutation score as CI gate<!-- Java pom.xml -->
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<configuration>
<mutationThreshold>70</mutationThreshold>
<coverageThreshold>80</coverageThreshold>
</configuration>
</plugin>#### Performance Testing
Required before major releases and for any change to a hot path:
// k6 example
export const options = {
thresholds: {
http_req_duration: ['p(99)<200'], // p99 must be under 200ms
http_req_failed: ['rate<0.01'], // Error rate < 1%
},
}#### Flaky Test Policy
Flaky tests are classified as blocking defects:
| Rule | Value |
|---|---|
| Fix or quarantine SLA | 2 business days |
| Maximum quarantine period | 2 sprints |
| Action after quarantine expires | Delete the test (rewrite from scratch) |
| Prohibited patterns | Thread.sleep(), time.sleep(), setTimeout in test assertions |
| Allowed retry | @RetryingTest (JUnit) only for inherently non-deterministic integration tests |
#### Coverage Thresholds
Enforced as a CI gate, PRs that drop coverage below threshold are blocked:
<!-- JaCoCo Maven config -->
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum>
</limit>
</limits>
</rule>#### Key Rules
Do not mock what you do not own. Only mock types you define. For third-party libraries (HTTP clients, ORMs, cloud SDKs), use:
Run the full test suite, never run a single test in isolation to verify a fix. A fix that makes one test pass but breaks another is not a fix.
#### Testcontainers Mandate
All integration tests that touch external systems (databases, message queues, caches, cloud services) must use Testcontainers:
@Testcontainers
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
}
}Never use a shared staging database for automated tests, tests must be hermetic and reproducible.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.