Enforce 2026 testing best practices with BLOCKING validation.
Validation Rules
BLOCKING Rules (exit 1)
Rule Check Example Violation
Test Location Tests must be in tests/ or tests/
src/utils/helper.test.ts
AAA Pattern Tests must have Arrange/Act/Assert structure No clear sections
Descriptive Names Test names must describe behavior test('test1')
No Shared State Tests must not share mutable state let globalVar = [] without reset
Coverage Threshold Coverage must be ≥ 80% 75% coverage
File Location Rules
ALLOWED: tests/unit/user.test.ts tests/integration/api.test.ts tests/components/Button.test.tsx app/tests/test_users.py tests/conftest.py
BLOCKED: src/utils/helper.test.ts # Tests in src/ components/Button.test.tsx # Tests outside test dir app/routers/test_routes.py # Tests mixed with source
Naming Conventions
TypeScript/JavaScript:
// GOOD - Descriptive, behavior-focused test('should return empty array when no items exist', () => {}) test('throws ValidationError when email is invalid', () => {}) it('renders loading spinner while fetching', () => {})
// BLOCKED - Too short, not descriptive test('test1', () => {}) test('works', () => {}) it('test', () => {})
Python:
GOOD - snake_case, descriptive
def test_should_return_user_when_id_exists(): def test_raises_not_found_when_user_missing():
BLOCKED - Not descriptive, wrong case
def testUser(): # camelCase def test_1(): # Not descriptive
AAA Pattern (Required)
Every test must follow Arrange-Act-Assert:
TypeScript Example
describe('calculateDiscount', () => { test('should apply 10% discount for orders over $100', () => { // Arrange const order = createOrder({ total: 150 }); const calculator = new DiscountCalculator();
// Act
const discount = calculator.calculate(order);
// Assert
expect(discount).toBe(15);
}); });
Python Example
class TestCalculateDiscount: def test_applies_10_percent_discount_over_threshold(self): # Arrange order = Order(total=150) calculator = DiscountCalculator()
# Act
discount = calculator.calculate(order)
# Assert
assert discount == 15
Test Isolation (Required)
Tests must not share mutable state:
// BLOCKED - Shared mutable state let items = [];
test('adds item', () => { items.push('a'); expect(items).toHaveLength(1); });
test('removes item', () => { // FAILS - items already has 'a' from previous test expect(items).toHaveLength(0); });
// GOOD - Reset state in beforeEach describe('ItemList', () => { let items: string[];
beforeEach(() => { items = []; // Fresh state each test });
test('adds item', () => { items.push('a'); expect(items).toHaveLength(1); });
test('starts empty', () => { expect(items).toHaveLength(0); // Works! }); });
Coverage Requirements
Area Minimum Target
Overall 80% 90%
Business Logic 90% 100%
Critical Paths 95% 100%
New Code 100% 100%
Running Coverage
TypeScript (Vitest/Jest):
npm test -- --coverage npx vitest --coverage
Python (pytest):
pytest --cov=app --cov-report=json
Parameterized Tests
Use parameterized tests for multiple similar cases:
TypeScript
describe('isValidEmail', () => { test.each([ ['user@example.com', true], ['invalid', false], ['@missing.com', false], ['user@domain.co.uk', true], ['user+tag@example.com', true], ])('isValidEmail(%s) returns %s', (email, expected) => { expect(isValidEmail(email)).toBe(expected); }); });
Python
import pytest
class TestIsValidEmail: @pytest.mark.parametrize("email,expected", [ ("user@example.com", True), ("invalid", False), ("@missing.com", False), ("user@domain.co.uk", True), ]) def test_email_validation(self, email: str, expected: bool): assert is_valid_email(email) == expected
Fixture Best Practices (Python)
import pytest
Function scope (default) - Fresh each test
@pytest.fixture def db_session(): session = create_session() yield session session.rollback()
Module scope - Shared across file
@pytest.fixture(scope="module") def expensive_model(): return load_ml_model() # Only loads once per file
Session scope - Shared across all tests
@pytest.fixture(scope="session") def db_engine(): engine = create_engine(TEST_DB_URL) yield engine engine.dispose()
Common Violations
- Test in Wrong Location
BLOCKED: Test file must be in tests/ directory File: src/utils/helpers.test.ts Move to: tests/utils/helpers.test.ts
- Missing AAA Structure
BLOCKED: Test pattern violations detected
- Tests should follow AAA pattern (Arrange/Act/Assert)
- Add comments or clear separation between sections
- Shared Mutable State
BLOCKED: Test pattern violations detected
- Shared mutable state detected. Use beforeEach to reset state.
- Coverage Below Threshold
BLOCKED: Coverage 75.2% is below threshold 80%
Actions required:
- Add tests for uncovered code
- Run: npm test -- --coverage
- Ensure coverage >= 80% before proceeding
Related Skills
-
integration-testing
-
Component interaction tests
-
e2e-testing
-
End-to-end with Playwright
-
msw-mocking
-
Network mocking
-
test-data-management
-
Fixtures and factories
Capability Details
aaa-pattern
Keywords: AAA, arrange act assert, test structure, test pattern Solves:
-
Enforce Arrange-Act-Assert pattern
-
Ensure clear test structure
-
Improve test readability
test-naming
Keywords: test name, test naming, descriptive test, test description Solves:
-
Enforce descriptive test names
-
Block generic test names like test1
-
Improve test documentation
test-location
Keywords: test location, test directory, tests folder, where tests Solves:
-
Validate test file placement
-
Block tests mixed with source
-
Enforce test directory structure
coverage-threshold
Keywords: coverage, test coverage, code coverage, 80%, threshold Solves:
-
Enforce minimum 80% coverage
-
Block merges with low coverage
-
Maintain quality standards
test-isolation
Keywords: test isolation, shared state, independent tests, flaky Solves:
-
Detect shared mutable state
-
Ensure test independence
-
Prevent flaky tests