7.0 KiB
7.0 KiB
Test Framework Documentation
Quick Start
# Run all tests (files ending in .spec.js)
npm test
# Run all test files (including test_*.js)
npm run test:all
# Run with watch mode (re-runs on file changes)
npm run test:watch
# Run a single test file
npm run test:single tests/sample.spec.js
# Run with coverage report
npm run test:coverage
# Coverage for all tests
npm run test:coverage-all
How Mocha Handles Test Failures
✅ Key Behaviors:
-
Runs ALL tests - Mocha does NOT stop at the first failure
- All tests are executed regardless of failures
- Summary shows total passing/failing at the end
-
Clear failure identification
- Each failure is numbered (1), 2), 3), etc.)
- Shows the full test path:
Suite > Subsuite > Test Name - Displays exact line number:
tests/sample.spec.js:25:25
-
Detailed error messages
- Shows expected vs actual values
- Color-coded diff (red for actual, green for expected)
- Full stack trace for debugging
-
Exit code indicates failures
- Exit code 0 = all tests passed
- Exit code > 0 = number of failures (capped at certain value)
- CI/CD systems can detect failures automatically
Example Output:
10 passing (106ms)
4 failing
1) Sample Test Suite - Basic Math
Addition
should handle zero (INTENTIONAL FAIL):
AssertionError: expected +0 to equal 1
at Context.<anonymous> (tests/sample.spec.js:25:25)
How to Locate Failed Tests:
- Line numbers: Click the link
tests/sample.spec.js:25:25in VSCode terminal - Test hierarchy: Follow the nested structure (Suite > Subsuite > Test)
- Search: Copy the test name and use Ctrl+F in your test file
- Summary: Scroll to top for count: "10 passing, 4 failing"
Test File Naming
*.spec.js- Unit/integration tests (run withnpm test)test_*.js- Manual test scripts (run withnpm run test:allor individually)
Writing Tests
Basic Structure:
const { expect } = require('chai');
describe('Feature Name', () => {
describe('Sub-feature', () => {
it('should do something specific', () => {
const result = myFunction();
expect(result).to.equal(expectedValue);
});
it('should handle edge cases', async () => {
const result = await asyncFunction();
expect(result).to.be.an('object');
expect(result.status).to.equal('success');
});
});
});
Common Assertions (Chai):
// Equality
expect(value).to.equal(42);
expect(obj).to.deep.equal({ a: 1, b: 2 });
// Types
expect(value).to.be.a('string');
expect(arr).to.be.an('array');
// Arrays
expect(arr).to.have.lengthOf(3);
expect(arr).to.include(item);
expect(arr).to.deep.equal([1, 2, 3]);
// Objects
expect(obj).to.have.property('name');
expect(obj.name).to.equal('test');
// Numbers
expect(num).to.be.above(10);
expect(num).to.be.at.least(5);
expect(num).to.be.below(100);
// Existence
expect(value).to.exist;
expect(value).to.be.null;
expect(value).to.be.undefined;
// Async (returns promise)
await expect(promise).to.be.fulfilled;
await expect(promise).to.be.rejected;
Test Lifecycle Hooks:
describe('Feature', () => {
before(() => {
// Runs once before all tests in this suite
});
after(() => {
// Runs once after all tests in this suite
});
beforeEach(() => {
// Runs before each test
});
afterEach(() => {
// Runs after each test
});
it('test 1', () => { /* ... */ });
it('test 2', () => { /* ... */ });
});
Async Tests:
// Using async/await (preferred)
it('should fetch data', async () => {
const data = await fetchData();
expect(data).to.exist;
});
// Using done callback
it('should call callback', (done) => {
asyncFunction((err, result) => {
expect(err).to.be.null;
expect(result).to.equal('success');
done();
});
});
Skipping Tests:
// Skip a single test
it.skip('should be skipped', () => { /* ... */ });
// Skip entire suite
describe.skip('Skipped Suite', () => { /* ... */ });
// Run only specific tests (useful for debugging)
it.only('should run only this test', () => { /* ... */ });
describe.only('Only Suite', () => { /* ... */ });
Coverage Reports
After running npm run test:coverage:
- Open
coverage/index.htmlin browser for detailed coverage report - Shows line, branch, function, and statement coverage
- Highlights uncovered lines in red
Environment Variables
Tests automatically load from environment.env via tests/setup.js.
To use a different env file:
npm run test:single -- tests/my_test.spec.js --env ./environment_prod.env
CI/CD Integration
Example GitHub Actions workflow:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm ci
- run: npm test
- run: npm run test:coverage
- uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
Best Practices
-
Test naming: Use descriptive names that explain what's being tested
- ✅
should return 404 when user not found - ❌
test user function
- ✅
-
One assertion per test: Focus each test on a single behavior
- Makes failures easier to diagnose
- Tests are more maintainable
-
Use beforeEach/afterEach: Keep tests independent
- Create fresh test data for each test
- Clean up after tests complete
-
Mock external services: Don't hit real APIs in unit tests
- Use
sinonfor mocking - Faster tests, no rate limits
- Use
-
Test data isolation: Use unique identifiers (timestamps)
- Prevents test conflicts
- Avoids cleanup issues
-
Rate limiting: Add delays between API calls (see STRIPE_RATE_LIMITING in copilot-instructions)
Troubleshooting
Tests hang and don't exit:
- Ensure async operations complete
- Close database/queue connections in
after()hooks - Use
--exitflag (already in npm scripts)
Environment variables not loaded:
- Check
tests/setup.jsis required - Verify
environment.envexists and has correct values
Can't find modules:
- Run
npm installto ensure all dependencies installed - Check file paths are relative to project root
Converting Existing Tests
To convert an existing test_*.js file to Mocha:
- Wrap test logic in
describeanditblocks - Replace console assertions with
expect()assertions - Remove manual environment loading (handled by setup.js)
- Rename to
*.spec.jsor keep astest_*.jsand run withnpm run test:all
Example:
// Before (manual script)
console.log('Testing addition...');
const result = 2 + 2;
if (result !== 4) {
console.error('FAILED: Expected 4, got', result);
process.exit(1);
}
console.log('✅ PASSED');
// After (Mocha)
const { expect } = require('chai');
describe('Math Operations', () => {
it('should add numbers correctly', () => {
const result = 2 + 2;
expect(result).to.equal(4);
});
});