@scom/test
v0.1.7
Published
Test runner for @scom packages
Readme
@scom/test
A lightweight, fast test runner for @scom packages built with TypeScript and Node.js. Designed to be a simple alternative to Jest with zero external dependencies.
Features
- 🚀 Zero dependencies: Uses only Node.js built-in modules (plus TypeScript and jsdom for execution)
- 📝 TypeScript first: Native TypeScript support with automatic transpilation
- 🧪 Jest-compatible API: Familiar testing syntax (
describe,it,expect, etc.) - ⚡ Fast execution: Lightweight runtime with minimal overhead
- 📊 Clear reporting: Detailed test results with timing and error information
- 🎯 Focused testing: Run specific files, directories, or patterns
- 🔧 CLI and programmatic: Use from command line or import as a module
Installation
npm install @scom/test --save-devNote: Use
npx @scom/testto run the CLI - npm automatically resolves this to the correct binary.
Quick Start
1. Write a test file
// tests/math.test.ts
describe('Math utilities', () => {
it('should add numbers correctly', () => {
expect(1 + 1).toBe(2);
});
it('should handle async operations', async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
});2. Run tests
# Run all tests
npx @scom/test
# Run specific test file
npx @scom/test tests/math.test.ts
# Run with verbose output
npx @scom/test --verboseUsage
Command Line Interface
# Run all TypeScript tests in current directory
npx @scom/test
# Run tests in specific directory
npx @scom/test tests/
# Run specific test file
npx @scom/test tests/server.test.ts
# Run with options
npx @scom/test --verbose --timeout 30000 tests/
# Show help
npx @scom/test --helpShell Compatibility
The @scom/test package uses npm's scoped package resolution, which automatically maps to the correct binary:
Recommended usage:
npx @scom/test --verbose tests/Alternative (via npm scripts):
# In package.json
{
"scripts": {
"test": "@scom/test"
}
}
# Then run:
npm test
npm run test -- --verbose # Pass additional argumentsPackage.json Scripts
{
"scripts": {
"test": "@scom/test",
"test:verbose": "@scom/test --verbose"
}
}Programmatic API
import { runTestFile, findTestFiles, TestResult } from '@scom/test';
// Run a specific test file
const result: TestResult = await runTestFile('./tests/server.test.ts');
console.log(`Tests: ${result.passed}/${result.total}`);
// Find and run all test files
const testFiles = await findTestFiles('./tests');
for (const file of testFiles) {
const result = await runTestFile(file);
console.log(`${file}: ${result.passed}/${result.total} passed`);
}Test API Reference
Test Structure
describe('Test Suite', () => {
// Setup and teardown
beforeAll(async () => {
// Run once before all tests in this suite
});
afterAll(async () => {
// Run once after all tests in this suite
});
beforeEach(() => {
// Run before each test
});
afterEach(() => {
// Run after each test
});
// Test cases
it('should do something', () => {
expect(true).toBe(true);
});
test('alternative syntax for it', async () => {
const result = await someAsyncFunction();
expect(result).toEqual({ success: true });
});
// Nested test suites
describe('Nested suite', () => {
it('can be nested', () => {
expect(1).toBeLessThan(2);
});
});
});Test Timeouts
Control test execution timeouts using scomTest.setTimeout():
// Set timeout for individual tests
it('should complete within custom timeout', async () => {
scomTest.setTimeout(5000); // 5 seconds for this test only
const result = await longRunningOperation();
expect(result).toBeDefined();
});
// Set timeout in beforeEach for all tests in a suite
describe('Long running operations', () => {
beforeEach(() => {
scomTest.setTimeout(10000); // 10 seconds for all tests in this suite
});
it('should handle slow database queries', async () => {
const data = await database.complexQuery();
expect(data).toHaveLength(100);
});
it('should process large files', async () => {
const result = await fileProcessor.processLargeFile();
expect(result.success).toBe(true);
});
});
// Set timeout for specific async operations
test('should timeout appropriately', async () => {
// Default timeout applies
await expect(quickOperation()).resolves.toBeDefined();
// Custom timeout for slow operation
scomTest.setTimeout(15000);
await expect(verySlowOperation()).resolves.toBeDefined();
// Reset to default timeout
scomTest.setTimeout(30000); // or omit to use global default
});Timeout Behavior
- Default timeout: 30 seconds (30000ms) for all tests
- Global timeout: Set via CLI
--timeoutoption - Test-specific timeout: Use
scomTest.setTimeout()within test functions - Suite-wide timeout: Use
scomTest.setTimeout()inbeforeEachorbeforeAll - Scope: Timeout changes only affect the current test or suite
- Reset: Timeout automatically resets to default after each test
Best Practices
describe('API Integration Tests', () => {
// Set longer timeout for all integration tests
beforeAll(() => {
scomTest.setTimeout(60000); // 1 minute for integration tests
});
it('should authenticate user', async () => {
// This test inherits the 60-second timeout
const token = await authService.login(credentials);
expect(token).toBeDefined();
});
it('should handle network delays', async () => {
// Override with even longer timeout for this specific test
scomTest.setTimeout(120000); // 2 minutes
const response = await api.callSlowEndpoint();
expect(response.status).toBe(200);
});
it('should use default timeout', async () => {
// Back to suite default (60 seconds)
const data = await api.getFastData();
expect(data).toBeDefined();
});
});Matchers
// Equality
expect(value).toBe(expected); // Strict equality (===)
expect(value).toEqual(expected); // Deep equality
expect(value).not.toBe(expected); // Negation
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(number).toBeGreaterThan(3);
expect(number).toBeLessThan(10);
expect(number).toBeCloseTo(3.14, 2); // Floating point comparison
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
// Arrays and Objects
expect(array).toContain(item);
expect(array).toHaveLength(3);
// Exceptions
expect(() => {
throw new Error('test');
}).toThrow();
expect(() => {
throw new Error('specific message');
}).toThrow('specific message');
// Async
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();CLI Options
| Option | Alias | Description | Default |
|--------|-------|-------------|---------|
| --verbose | -v | Show detailed output including passing tests | false |
| --pattern | -p | Test file pattern | **/*.test.{ts,tsx} |
| --testNamePattern | -t | Filter tests by test name pattern | - |
| --timeout | - | Test timeout in milliseconds | 30000 |
| --bail | -b | Stop on first test failure | false |
| --help | -h | Show help message | - |
Configuration
The test runner currently uses default configuration. Configuration file support may be added in future versions.
Migration from Jest
Install @scom/test:
npm uninstall jest @types/jest npm install @scom/test --save-devUpdate package.json:
{ "scripts": { "test": "@scom/test" } }Convert test files: Ensure all test files use
.test.tsextensionRemove Jest config: Delete
jest.config.jsand Jest configuration frompackage.jsonUpdate imports: Most Jest APIs are compatible, but check for any Jest-specific utilities
Examples
Basic Test
// tests/calculator.test.ts
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
calc = new Calculator();
});
it('should add two numbers', () => {
expect(calc.add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(calc.add(-1, 1)).toBe(0);
});
});Async Test
// tests/api.test.ts
describe('API calls', () => {
it('should fetch data', async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveProperty('results');
});
it('should handle errors', async () => {
await expect(
fetch('https://api.example.com/invalid')
).rejects.toThrow();
});
it('should handle slow API calls', async () => {
// Set custom timeout for slow API
scomTest.setTimeout(60000); // 1 minute
const response = await fetch('https://slow-api.example.com/data');
expect(response.status).toBe(200);
});
});Mock Functions
Basic mock functionality is available:
// tests/service.test.ts
describe('UserService', () => {
it('should call the API', () => {
const mockFetch = scomTest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: 1, name: 'John' })
});
global.fetch = mockFetch;
// Test your service
expect(mockFetch).toHaveBeenCalledWith('/api/users/1');
});
});Troubleshooting
Common Issues
- TypeScript compilation errors: Ensure
tsconfig.jsonis properly configured - Module resolution: Use relative imports or configure path mapping
- Async test timeouts: Use
scomTest.setTimeout()to increase timeout or CLI--timeoutoption - File not found: Check test file patterns and paths
Debug Mode
Run with verbose output to see detailed information:
npx @scom/test --verbose tests/Contributing
- Fork the repository
- Create a feature branch
- Add tests for your changes
- Run the test suite:
npm test - Submit a pull request
