skills/testing-strategy/SKILL.md
Test naming conventions, fixture patterns, mocking strategies, and coverage thresholds by stage
npx skillsauth add navraj007in/architecture-cowork-plugin Testing StrategyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Covers unit, integration, and e2e test patterns for all supported frameworks.
src/services/user.ts → src/services/__tests__/user.test.ts (Jest/Vitest)src/services/user.ts → src/services/test_user.py (pytest)tests/ directory at project root
tests/integration/auth.test.tstests/integration/database.test.tse2e/ or tests/e2e/ directory
e2e/flows/login.test.tse2e/flows/checkout.test.ts<module>.test.ts or <module>_test.py<feature>.integration.test.ts or test_<feature>.py<flow>.e2e.test.ts or test_<flow>_e2e.pyAll tests MUST follow AAA pattern:
describe('UserService', () => {
describe('createUser', () => {
it('should create a user with valid email and name', () => {
// ARRANGE: Setup test data, mocks, fixtures
const email = '[email protected]';
const name = 'Alice Chen';
// ACT: Call the function under test
const user = service.createUser(email, name);
// ASSERT: Verify the result
expect(user.email).toBe(email);
expect(user.name).toBe(name);
expect(user.id).toBeDefined();
});
});
});
Golden rule: One logical assertion per test (one reason to fail).
❌ BAD:
it('works', () => { ... });
it('user creation 1', () => { ... });
✅ GOOD:
it('should create a user with valid email and return user object with id', () => { ... });
it('should reject email without @ symbol', () => { ... });
it('should hash password before storing in database', () => { ... });
Pattern: should [what happens] [given conditions if not obvious]
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: ['**/__tests__/**/*.ts', '**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/index.ts'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
// tsconfig.test.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react",
"types": ["jest", "node"]
}
}
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py *_test.py
python_classes = Test*
python_functions = test_*
addopts = --verbose --strict-markers
markers =
unit: unit tests
integration: integration tests
slow: slow running tests
# conftest.py — shared fixtures
import pytest
@pytest.fixture
def sample_user():
return {'id': 1, 'email': '[email protected]', 'name': 'Test User'}
// *_test.go convention
package user
import "testing"
func TestCreateUser(t *testing.T) {
user := CreateUser("[email protected]", "Alice")
if user.Email != "[email protected]" {
t.Errorf("expected %q, got %q", "[email protected]", user.Email)
}
}
public class UserServiceTests
{
[Fact]
public void CreateUser_WithValidEmail_ReturnsUserWithId()
{
// Arrange
var service = new UserService();
// Act
var user = service.CreateUser("[email protected]", "Alice");
// Assert
Assert.NotNull(user.Id);
Assert.Equal("[email protected]", user.Email);
}
}
Use in-memory database for unit tests when possible:
// jest.config.js for Node backend
module.exports = {
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts']
};
// tests/setup.ts
import { PrismaClient } from '@prisma/client';
beforeAll(async () => {
// Point to test database
process.env.DATABASE_URL = 'file:./test.db';
await prisma.$executeRawUnsafe('PRAGMA foreign_keys = OFF');
});
afterEach(async () => {
// Clean up between tests
await prisma.$queryRaw`DELETE FROM "User"`;
await prisma.$queryRaw`DELETE FROM "Post"`;
});
afterAll(async () => {
await prisma.$disconnect();
});
Mock HTTP calls, external APIs, payment gateways:
// jest.mock for modules
jest.mock('../lib/stripe', () => ({
createPayment: jest.fn().mockResolvedValue({ id: 'pay_123', status: 'succeeded' })
}));
// Manual mock for fetch
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: [] })
})
);
Use actual test database for integration tests:
// tests/integration/setup.ts
beforeAll(async () => {
// Spin up test PostgreSQL via docker-compose.test.yml
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5433/test_db';
await prisma.$executeRawUnsafe('CREATE SCHEMA IF NOT EXISTS tests');
});
afterAll(async () => {
await prisma.$executeRawUnsafe('DROP SCHEMA tests CASCADE');
});
Rule: If a test needs the database to run, it's an integration test, not a unit test. Don't mock the database for integration tests.
// tests/fixtures/user.fixtures.ts
export const validUser = {
email: '[email protected]',
name: 'Alice Chen',
password: 'SecurePass123!'
};
export const adminUser = {
...validUser,
role: 'admin'
};
export const inactiveUser = {
...validUser,
status: 'inactive'
};
// In test file
import { validUser, adminUser } from './fixtures/user.fixtures';
it('should allow admin to create users', () => {
const created = service.createUser(validUser, { createdBy: adminUser });
expect(created.createdBy).toBe(adminUser.id);
});
// tests/factories/user.factory.ts
class UserFactory {
static async create(overrides = {}) {
const defaults = {
email: `user-${Date.now()}@example.com`,
name: 'Test User',
role: 'user'
};
return prisma.user.create({
data: { ...defaults, ...overrides }
});
}
static async createMany(count, overrides = {}) {
return Promise.all(
Array.from({ length: count }).map(() => this.create(overrides))
);
}
}
// In test
const users = await UserFactory.createMany(10, { role: 'admin' });
expect(users).toHaveLength(10);
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:unit": "jest --testPathPattern=__tests__",
"test:integration": "jest --testPathPattern=integration",
"test:e2e": "playwright test"
}
}
pytest # all tests
pytest -m unit # unit tests only
pytest -m integration # integration tests
pytest -v --cov=src # with coverage
go test ./... # all tests
go test -cover ./... # with coverage
go test -run TestCreateUser # specific test
// Always return promise or use async/await
it('should fetch user by id', async () => {
const user = await service.getUserById(1);
expect(user.id).toBe(1);
});
// Expect promises to resolve/reject
it('should reject invalid id', async () => {
await expect(service.getUserById(-1)).rejects.toThrow('Invalid ID');
});
import pytest
@pytest.mark.asyncio
async def test_fetch_user():
user = await service.get_user(1)
assert user.id == 1
# Or use sync wrapper
from asyncio import run
def test_sync_wrapper():
user = run(service.get_user(1))
assert user.id == 1
// Jest
it.skip('should handle network timeout', () => {
// Re-enable after fixing flakiness
});
// pytest
@pytest.mark.skip(reason="flaky, TODO: fix race condition")
def test_concurrent_updates():
pass
// jest.config.js
module.exports = {
testTimeout: 30000 // 30s for slow tests
};
// In test
jest.setTimeout(5000); // override for this test
it.todo('should support bulk user import');
- name: Run Tests
run: npm test -- --coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
- name: Check Coverage
run: |
COVERAGE=$(npm test -- --coverage | grep Lines | awk '{print $NF}' | sed 's/%//')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% below threshold 80%"
exit 1
fi
development
# Trade-Off Analysis Skill Quantifies exact trade-offs when switching between architecture options. Shows users precisely what they gain and lose when choosing Option A over Option B. ## When to Use Use this skill to help users decide between options by showing: 1. **Cost difference** — how much more/less per month? 2. **Performance difference** — how much faster/slower? 3. **Complexity difference** — how much harder to build/maintain? 4. **Scalability difference** — when does this option hit
testing
# Stage Detection Skill Detects the current project stage (concept → mvp → growth → enterprise) based on `_state.json` field presence and completeness. Used by `/architect:next-steps`, `/architect:check-state`, and roadmap commands. ## When to Use Invoke this skill when you need to determine what stage a project is at based on its state file. Stage detection drives: - Command recommendations (what to run next) - Required fields validation (what should exist at this stage) - Risk assessment (w
development
# Stack Swap Simulator Skill Estimates cost and effort to switch from one tech stack to another. Helps answer: "Can we migrate later if needed?" ## When to Use Use this skill to understand: 1. **Cost of switching stacks** — engineer weeks + downtime risk 2. **Timeline to switch** — how long is the project? 3. **Risk of switching** — what can go wrong? 4. **ROI of switching** — does it save money long-term? 5. **Backwards compatibility** — can we do a gradual migration? ## Input Provide sour
tools
# Stack Compatibility Skill Verifies that chosen technologies integrate well together. Prevents "I picked these tools and they don't work well together" regrets. ## When to Use Use this skill to verify: 1. **Chosen tools work together** — React + Node + MongoDB = good? 2. **No hidden incompatibilities** — will I hit issues in production? 3. **Team can support it** — do we have expertise for this combo? 4. **Licenses compatible** — can we use these together commercially? 5. **Performance assum