test-fundamentals
Core testing principles applying to all unit and integration tests
SKILL.md
| Name | test-fundamentals |
| Description | Core testing principles applying to all unit and integration tests |
name: test-fundamentals description: Core testing principles applying to all unit and integration tests
Testing Core Rules
General testing principles that apply to ALL tests (unit and integration).
See code-comment-style skill for comment guidelines
🚨 CRITICAL RULES (Immediate Failure)
1. Zero Test Failure Tolerance
🚨 CRITICAL: ANY existing test failure STOPS the update process
- ANY existing test failure STOPS the update process
- NO excuses for test failures
- Determine cause and fix immediately
- Never disable or skip tests to proceed
RULE: Before starting any work, run existing tests and ensure they ALL pass.
# Check current state
npm run test
# If ANY test fails:
# 1. STOP all work
# 2. Analyze the failure
# 3. Fix the implementation
# 4. Re-run tests until ALL pass
Why:
- Prevents introducing new bugs
- Maintains code quality
- Ensures reliable baseline
2. Test Coverage Requirements
🚨 CRITICAL: ALL new operations MUST have test coverage
- ALL new operations MUST have unit test coverage
- ALL new operations MUST have integration tests
- Integration tests run when credentials available, fail naturally when not
- Never use
.skip()on individual tests - Tests should be written and ready to work
Requirements:
- ✅ Every operation has unit tests
- ✅ Every operation has integration tests
- ✅ Both success and error cases tested
- ✅ 100% pass rate for available tests
Example:
// ✅ CORRECT: Both unit and integration tests
test/unit/UserProducerTest.ts // Unit test with nock
test/integration/UserProducerTest.ts // Integration test with real API
// ❌ WRONG: Only one test type
test/unit/UserProducerTest.ts // Missing integration test
3. Test Preservation
🚨 CRITICAL: EXISTING tests must pass throughout entire workflow
- EXISTING tests must pass throughout entire workflow
- NEVER modify existing tests during updates
- Add new tests separately
Why:
- Prevents breaking existing functionality
- Maintains test suite integrity
- Ensures backward compatibility
Exceptions: Only in 'fix' workflow when API has breaking changes (see Exceptions section)
4. API Validation Before Implementation
🚨 CRITICAL: Test every operation with curl/Node.js FIRST
Before implementing any operation:
- Test with curl or Node.js script
- Capture real API responses
- Save as test fixtures
- Verify credentials work
# Example: Test API endpoint before implementing
curl -X GET "https://api.example.com/users/123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json"
# Capture response, sanitize PII, save as fixture
# THEN implement the operation
Why:
- Verifies API works as documented
- Captures real response structure
- Identifies auth issues early
- Provides fixtures for unit tests
5. Test Failure Resolution Protocol
🚨 CRITICAL: MANDATORY when tests fail
When tests fail, follow this protocol:
- ANALYZE the failure - understand why
- FIX THE IMPLEMENTATION - correct bugs in src/
- NEVER SKIP TESTS - no .skip(), .only(), or comments
- NEVER MODIFY GENERATED FILES
- RE-RUN TESTS - execute until ALL pass
- REPEAT UNTIL ZERO FAILURES
PROHIBITED:
- Using
.skip()on individual tests - Using
.only()to run only passing tests - Commenting out failing tests
- Accepting any failures as "expected"
- Modifying test expectations to match wrong behavior
Required Pass Rates:
- Unit Tests: 100% pass rate
- Integration Tests: 100% when credentials available
- Lint: Zero errors, zero warnings
- Build: Exit code 0
Example of CORRECT approach:
// Test fails: expected 'John' but got 'John Doe'
// ❌ WRONG: Skip the test
it.skip('should get user name', async () => { ... });
// ❌ WRONG: Change expectation
expect(user.name).to.equal('John Doe'); // Changed to match wrong output
// ✅ CORRECT: Fix the implementation
// Fix the mapping in src/Mappers.ts to extract first name correctly
// Re-run test until it passes
🟡 STANDARD RULES
Test Naming
Use descriptive test names explaining the scenario:
// ✅ GOOD - Clear, specific
it('should retrieve user by ID with correct type mapping', async () => { ... });
it('should throw NoSuchObjectError when user not found', async () => { ... });
it('should map email string to Email type', async () => { ... });
// ❌ BAD - Vague, unclear
it('should work', async () => { ... });
it('test get user', async () => { ... });
Use 'should' for expected behaviors:
- ✅ "should retrieve user by ID"
- ✅ "should throw error when invalid ID"
- ✅ "should map API response to core types"
Include failure cases:
- ✅ "should throw error when user not found"
- ✅ "should handle authentication failure"
- ✅ "should return empty array when no results"
Test Execution Order
Run tests in this order:
-
Unit tests first (fast feedback)
npm run test:unit -
Integration tests with credentials
npm run test:integration -
Lint and build
npm run lint npm run build -
All must pass for task completion
Why this order:
- Unit tests are fast, catch most issues
- Integration tests verify real API behavior
- Lint catches style issues
- Build verifies TypeScript compilation
Test Structure
Standard describe/it structure:
describe('{ClassName}', () => {
describe('{methodName}', () => {
it('should {expected behavior}', async () => {
// Arrange - Set up test data
// Act - Execute the method
// Assert - Verify the result
});
it('should {handle error case}', async () => {
// Arrange - Set up error condition
// Act - Execute the method
// Assert - Verify error handling
});
});
});
Key elements:
- ✅ Outer describe for class/module
- ✅ Inner describe for method
- ✅ Multiple it() for different scenarios
- ✅ Arrange/Act/Assert pattern
🟡 STANDARD RULES
Debug Logging (MANDATORY for Integration Tests)
ALL integration tests MUST include debug logging to enable:
- Runtime mapper validation
- Test failure debugging
- API response verification
Standard Pattern:
import { LoggerEngine } from '@zerobias-org/logger';
const logger = LoggerEngine.root();
describe('User Producer Tests', function() {
it('should list users', async function() {
const result = await userApi.list(organizationId);
// MANDATORY: Log operation and result
logger.debug(`userApi.list(${organizationId})`, JSON.stringify(result, null, 2));
expect(result).to.not.be.null;
// ... assertions
});
});
Requirements:
- ✅ Import logger from
@zerobias-org/logger - ✅ Create logger with LOG_LEVEL from environment
- ✅ Log AFTER every API operation call
- ✅ Include operation name and parameters in log
- ✅ Include full result as JSON.stringify
Visibility:
# Normal run - no debug output
npm run test:integration
# Debug run - full logging
env LOG_LEVEL=debug npm run test:integration
Benefits:
- Enables runtime mapper field validation
- Quick debugging of test failures
- Documents actual API behavior
- Verifies responses match expectations
RULE: Integration tests without debug logging are INCOMPLETE.
🟢 GUIDELINES
Documentation
Document test setup in USERGUIDE.md:
## Testing
### Unit Tests
Unit tests run without credentials and use mocked HTTP interactions:
\`\`\`bash
npm run test:unit
\`\`\`
### Integration Tests
Integration tests require credentials in a `.env` file:
\`\`\`bash
# .env
SERVICE_API_KEY=your-api-key
SERVICE_EMAIL=your-email
SERVICE_PASSWORD=your-password
\`\`\`
Run integration tests:
\`\`\`bash
npm run test:integration
# With debug logging:
LOG_LEVEL=debug npm run test:integration
\`\`\`
### Full Test Suite
Run all tests:
\`\`\`bash
npm test
\`\`\`
Skipped Tests
When tests can be skipped (ONLY for integration test suites):
// ✅ ALLOWED: Skip entire integration suite if no credentials
describe('Service Integration', function () {
before(function () {
if (!hasCredentials()) {
this.skip(); // Skip whole suite
}
});
});
// ❌ FORBIDDEN: Skip individual tests
it.skip('should get user', async () => { ... });
Document skipped tests in output:
- Integration tests should log when skipped due to missing credentials
- User should see clear message about how to enable tests
Exceptions
When Tests Can Be Modified
Tests can ONLY be modified in these scenarios:
-
'fix' workflow (not 'update')
- When fixing existing bugs
- When API has breaking changes
- When correcting incorrect test logic
-
API Breaking Changes
- When API endpoint changes
- When response structure changes
- When authentication method changes
-
Incorrect Test Logic
- When test expectations were wrong
- When test setup was incorrect
- When test assertions don't match requirements
ALWAYS document reason in commit message:
fix: update UserProducer tests for API breaking change
The API changed the user endpoint from /users/:id to /v2/users/:id
and added a required 'version' field in the response.
Updated tests to match new API contract.
Integration Test Exceptions
Integration tests have special skip rules:
- Can skip entire suite if no credentials (using
this.skip()in before hook) - Must document in test output
- Should provide instructions for enabling
before(function () {
if (!hasCredentials()) {
console.log('Skipping integration tests: no credentials in .env');
console.log('Create a .env file with SERVICE_API_KEY to enable');
this.skip();
}
});
Validation
Validate general testing rules:
# Check test files exist
ls -lh test/unit/
ls -lh test/integration/
# Run all tests
npm run clean
npm run build
npm run test
npm run test:integration # If credentials available
# Check for skipped tests (should only be integration suites without creds)
npm test 2>&1 | grep -i "skip"
# Check for test failures (should be none)
npm test 2>&1 | grep -i "fail" && echo "❌ FAIL: Tests failing" || echo "✅ PASS"
# Check build exits with 0
npm run build && echo "✅ PASS: Build successful" || echo "❌ FAIL: Build failed"
# Check lint passes
npm run lint && echo "✅ PASS: Lint successful" || echo "❌ FAIL: Lint failed"
Expected results:
- ✅ All tests pass
- ✅ Only integration suites skip when no credentials
- ✅ No individual tests skipped
- ✅ Build exits with code 0
- ✅ Lint passes with zero errors/warnings
Comment Style for Tests
See code-comment-style skill for complete guidelines
Key Rules for Test Code
NEVER comment obvious assertions:
// ❌ WRONG - Comments restate assertions
// Required fields
expect(entry).to.have.property('id');
expect(entry).to.have.property('name');
// ID should be a string
expect(entry.id).to.be.a('string');
// Validate structure
const groups = groupsResult.items;
expect(groups).to.be.an('array');
// ✅ CORRECT - Assertions are self-documenting
expect(entry).to.have.property('id');
expect(entry).to.have.property('name');
expect(entry.id).to.be.a('string');
const groups = groupsResult.items;
expect(groups).to.be.an('array');
DO comment non-obvious test logic:
// ✅ GOOD - Explains special test case
// Unicode group names require special URL encoding on this endpoint
const result = await groupApi.get(orgId, '测试组');
// ✅ GOOD - Explains API quirk being tested
// API returns 400 for invalid credentials but 404 for missing users
// We normalize both to NotConnectedError for consistent handling
try {
await userApi.get(invalidUserId);
} catch (error) {
expect(error).to.be.instanceOf(NotConnectedError);
}
Use Arrange/Act/Assert pattern without comments:
// ❌ WRONG - Structural comments
it('should get user by ID', async () => {
// Arrange - Set up test data
const userId = 'test-123';
// Act - Execute the method
const result = await userApi.get(orgId, userId);
// Assert - Verify the result
expect(result).to.not.be.null;
expect(result.id).to.equal(userId);
});
// ✅ CORRECT - Blank lines show structure
it('should get user by ID', async () => {
const userId = 'test-123';
const result = await userApi.get(orgId, userId);
expect(result).to.not.be.null;
expect(result.id).to.equal(userId);
});
Reference: See code-comment-style.md for full guidelines