TSpec is a modern, TypeScript-first testing framework designed for seamless testing of TypeScript applications. With zero-config setup, native TypeScript support, and a comprehensive feature set, TSpec makes testing TypeScript code intuitive and powerful.
# Install TSpec in your project
npm install --save-dev @tspec/core @tspec/assert @tspec/mock @tspec/cli
# Or install globally
npm install -g @tspec/cli
Create a test file math.tspec.ts
:
import { describe, test } from '@tspec/core';
import { expect } from '@tspec/assert';
describe('Math Operations', () => {
test('addition works correctly', () => {
expect(2 + 2).toBe(4);
expect(10 + 5).toBe(15);
});
test('handles floating point precision', () => {
expect(0.1 + 0.2).toBeCloseTo(0.3);
});
});
Run your tests:
# If installed locally
npx tspec
# If installed globally
tspec
# Watch mode - automatically re-run tests when files change
tspec --watch
Note: TSpec is currently in development. For now, you'll need to build from source (see Contributing section).
- Features
- Installation & Setup
- Basic Usage
- API Reference
- Configuration
- CLI Reference
- Examples
- Architecture
- Roadmap
- Contributing
- Native TypeScript support with full type safety
- Zero configuration required for basic usage
- Automatic TypeScript compilation with esbuild
- IntelliSense support for all APIs
- Test Organization:
describe()
,test()
,it()
for structuring tests - Rich Assertions: Comprehensive assertion methods with async support
- Async Support: Full Promise testing with
resolves
/rejects
- Mocking System: Function mocks, spies, and object method replacement
- Fast Execution: Optimized test runner with minimal overhead
- Watch Mode: Automatic test re-runs when files change with smart test selection
- Interactive Commands: Control test execution with keyboard shortcuts (a/f/q/Enter)
- Clear Output: Detailed test results with timing information
- Flexible CLI: Multiple options for different workflows
- Configuration: TypeScript config files with intelligent defaults
- File Discovery: Automatic test file detection with customizable patterns
- Error Handling: Comprehensive error reporting and stack traces
- Exit Codes: Proper process exit codes for CI/CD integration
- Verbose Modes: Detailed logging for debugging test issues
- Node.js 20+
- TypeScript knowledge
- npm or yarn
After setup, your project will have:
your-project/
βββ tests/
β βββ unit.tspec.ts
β βββ integration.tspec.ts
β βββ e2e.tspec.ts
βββ tspec.config.ts # Optional configuration
βββ package.json
TSpec works with your existing tsconfig.json
. For optimal experience:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true
}
}
import { describe, test, it } from '@tspec/core';
import { expect } from '@tspec/assert';
describe('User Service', () => {
test('creates user successfully', () => {
const user = { id: 1, name: 'Alice' };
expect(user.name).toBe('Alice');
expect(user.id).toBe(1);
});
it('validates user data', () => {
const invalidUser = { name: '' };
expect(() => validateUser(invalidUser)).toThrow('Name is required');
});
});
describe('API Client', () => {
test('fetches user data', async () => {
const userPromise = fetchUser('123');
await expect(userPromise).resolves.toEqual({
id: '123',
name: 'John Doe'
});
});
test('handles network errors', async () => {
const failedRequest = fetchUser('invalid-id');
await expect(failedRequest).rejects.toThrow('User not found');
});
});
import { fn, spyOn } from '@tspec/mock';
describe('Service with Dependencies', () => {
test('mocks external dependencies', () => {
const mockApiCall = fn().mockResolvedValue({ data: 'mocked' });
const service = new UserService(mockApiCall);
service.getUser('123');
expect(mockApiCall.toHaveBeenCalledWith('123')).toBe(true);
});
test('spies on existing methods', () => {
const service = new UserService();
const logSpy = spyOn(console, 'log');
service.logUser({ name: 'Alice' });
expect(logSpy.toHaveBeenCalledWith('User: Alice')).toBe(true);
logSpy.mockRestore();
});
});
TSpec includes a powerful watch mode for development that automatically re-runs tests when files change:
# Start watch mode
tspec --watch
# Watch with verbose output
tspec --watch --verbose
# Watch specific test patterns
tspec --watch --testMatch "**/*.unit.tspec.ts"
Watch Mode Features:
- π Smart Test Selection: Only runs tests affected by changed files
- β¨οΈ Interactive Commands: Control execution with keyboard shortcuts
- π Clear Output: Shows file changes and test results with timestamps
- β‘ Fast Feedback: Debounced file watching prevents rapid re-runs
Interactive Commands:
- Press
a
to run all tests - Press
f
to run only failed tests - Press
h
or?
for help - Press
q
to quit watch mode - Press
Enter
to re-run affected tests
Example Watch Session:
π TSpec Watch Mode Started
π Found 12 test files
β
Tests: 24 passed, 0 failed, 24 total
π Watching for changes...
β¨οΈ Interactive Commands:
β’ Press "a" to run all tests
β’ Press "f" to run only failed tests
β’ Press "q" to quit
[10:30:15] π File change: src/calculator.ts
Running 3 affected tests...
β
Tests: 3 passed, 0 failed, 3 total
π Ran 1 affected test files
describe(name: string, fn: () => void): void
Groups related tests together.
test(name: string, fn: () => void | Promise<void>, timeout?: number): void
it(name: string, fn: () => void | Promise<void>, timeout?: number): void
Defines individual test cases. it
is an alias for test
.
getSuites(): TestSuite[]
Returns all registered test suites.
clearSuites(): void
Clears all registered test suites.
class TestRunner {
async runTest(suite: string, test: Test): Promise<TestResult>
async runSuite(suite: TestSuite): Promise<TestResult[]>
getResults(): TestResult[]
printResults(): void
}
expect(actual).toBe(expected) // Strict equality (===)
expect(actual).toEqual(expected) // Deep equality
expect(actual).toBeNull() // Checks for null
expect(actual).toBeUndefined() // Checks for undefined
expect(actual).toBeTruthy() // Checks for truthy value
expect(actual).toBeFalsy() // Checks for falsy value
expect(fn).toThrow() // Function throws any error
expect(fn).toThrow('message') // Function throws specific message
expect(fn).toThrow(/pattern/) // Function throws matching pattern
expect(array).toContain(item) // Array contains item
expect(string).toContain(substring) // String contains substring
expect(string).toMatch(/pattern/) // String matches regex
expect(number).toBeCloseTo(expected, precision?) // Floating point comparison
// Promise resolves to expected value
await expect(promise).resolves.toBe(value)
await expect(promise).resolves.toEqual(object)
await expect(promise).resolves.toContain(item)
// Promise rejects with expected error
await expect(promise).rejects.toEqual(error)
await expect(promise).rejects.toThrow('message')
await expect(promise).rejects.toMatch(/pattern/)
import { fn, mock } from '@tspec/mock';
const mockFn = fn() // Create basic mock
const mockFn = fn(implementation) // Create mock with implementation
const mockFn = mock(implementation, options) // Create named mock
mockFn.mockReturnValue(value) // Set return value
mockFn.mockReturnValueOnce(value) // Set one-time return value
mockFn.mockImplementation(fn) // Set implementation
mockFn.mockImplementationOnce(fn) // Set one-time implementation
mockFn.mockResolvedValue(value) // Return resolved promise
mockFn.mockRejectedValue(error) // Return rejected promise
mockFn.mockThrow(error) // Throw error
mockFn.mockThrowOnce(error) // Throw error once
mockFn.toHaveBeenCalled() // Was called at least once
mockFn.toHaveBeenCalledTimes(count) // Called exact number of times
mockFn.toHaveBeenCalledWith(...args) // Returns boolean: called with specific arguments
mockFn.toHaveBeenLastCalledWith(...args) // Last call had specific arguments
mockFn.toHaveReturnedWith(value) // Returned specific value
mockFn.mockClear() // Clear call history
mockFn.mockReset() // Reset to initial state
mockFn.mockRestore() // Restore original function
import { spyOn } from '@tspec/mock';
const spy = spyOn(object, 'methodName') // Spy on method
spy.mockRestore() // Restore original method
Create tspec.config.ts
in your project root:
import { TSpecConfig } from '@tspec/core';
const config: TSpecConfig = {
// Test discovery
testMatch: [
'**/*.tspec.ts',
'**/*.test.ts',
'**/*.spec.ts'
],
testIgnore: [
'**/node_modules/**',
'**/dist/**',
'**/build/**'
],
// Execution
timeout: 10000,
// Output
verbose: false,
silent: false,
// Setup (basic configuration for now)
setupFilesAfterEnv: [],
// TypeScript support
extensionsToTreatAsEsm: ['.ts']
};
export default config;
Option | Type | Default | Description |
---|---|---|---|
testMatch |
string[] |
['**/*.tspec.ts'] |
Patterns for test files |
testIgnore |
string[] |
['**/node_modules/**', '**/dist/**'] |
Patterns to ignore |
timeout |
number |
5000 |
Default test timeout (ms) |
verbose |
boolean |
false |
Enable detailed output |
silent |
boolean |
false |
Suppress non-error output |
setupFilesAfterEnv |
string[] |
[] |
Setup files to run after test environment setup |
# Run all tests
tspec
# Show help
tspec --help
tspec -h
# Show version
tspec --version
tspec -v
# Configuration
tspec --config custom.config.ts # Use custom config file
tspec -c custom.config.ts
# Output control
tspec --verbose # Detailed output
tspec --silent # Errors only
# Test selection
tspec --testMatch "**/*.unit.ts" # Custom test pattern
tspec --testMatch "src/**/*.test.ts" # Multiple patterns
tspec "**/user*.tspec.ts" # Positional patterns
# Execution
tspec --timeout 30000 # Override timeout
# Run tests with detailed output
tspec --verbose
# Run only unit tests
tspec --testMatch "**/*.unit.tspec.ts"
# Run tests silently (CI mode)
tspec --silent
# Use custom configuration
tspec --config test-config.ts
# Run specific test files
tspec "user.tspec.ts" "auth.tspec.ts"
# Override timeout for slow tests
tspec --timeout 60000
0
- All tests passed1
- One or more tests failed or error occurred
import { describe, test } from '@tspec/core';
import { expect } from '@tspec/assert';
import { fn } from '@tspec/mock';
interface User {
id: string;
name: string;
email: string;
}
class ApiClient {
constructor(private baseUrl: string) {}
async getUser(id: string): Promise<User> {
const response = await fetch(`${this.baseUrl}/users/${id}`);
return response.json();
}
}
describe('API Client', () => {
test('fetches user successfully', async () => {
const mockFetch = fn().mockResolvedValue({
json: () => Promise.resolve({
id: '123',
name: 'John Doe',
email: '[email protected]'
})
});
global.fetch = mockFetch;
const client = new ApiClient('https://api.example.com');
const user = await client.getUser('123');
expect(user).toEqual({
id: '123',
name: 'John Doe',
email: '[email protected]'
});
expect(mockFetch.toHaveBeenCalledWith('https://api.example.com/users/123')).toBe(true);
});
});
TSpec is built as a monorepo with focused packages:
tspec/
βββ packages/
β βββ core/ # Test runner and framework core
β βββ assert/ # Assertion library
β βββ mock/ # Mocking utilities
β βββ cli/ # Command-line interface
βββ examples/ # Example test files
βββ docs/ # Additional documentation
- Uses glob patterns to find
.tspec.ts
files - Automatic file discovery with configurable patterns
- Configurable ignore patterns for excluding directories
- Supports custom test file extensions
- Sequential test execution
- Individual test isolation
- Comprehensive error handling and stack traces
- Result aggregation and reporting
- Native TypeScript compilation with esbuild
- Type-safe assertion methods with generics
- Strongly-typed mock functions
- Full IntelliSense support throughout
- TypeScript-First: Built specifically for TypeScript developers
- Zero Config: Works immediately with sensible defaults
- Developer Experience: Clear APIs and helpful error messages
- Simplicity: Focused feature set without unnecessary complexity
- Reliability: Stable core with predictable behavior
For information about current features, planned functionality, and development progress, see our Roadmap.
We welcome contributions! Please see our Contributing Guide for details.
If you want to contribute to TSpec development:
# Clone the repository
git clone https://github.com/oliver-richman/tspec.git
cd tspec
# Install dependencies
npm install
# Build all packages
npm run build --workspaces
# Run tests
node packages/cli/dist/index.js
# Run tests with verbose output
node packages/cli/dist/index.js --verbose
# Build specific package
npm run build --workspace=packages/core
# Watch mode for development
npm run dev --workspace=packages/core
TSpec is currently in active development. To use it now:
- Clone and build from source (instructions above)
- The framework is fully functional for basic testing
- Ready for basic TypeScript testing and mocking
- Package publication coming soon
MIT License - see LICENSE file for details.
TSpec is inspired by the best parts of Jest, Vitest, and other testing frameworks, while focusing specifically on TypeScript developer experience.
TSpec - Making TypeScript testing simple, powerful, and enjoyable.
For more information, see our documentation links above.
- Getting Started Guide - Step-by-step introduction to TSpec
- API Reference - Complete API documentation for all packages
- Configuration Guide - Advanced configuration options
- Examples - Real-world testing examples and patterns
TSpec is currently in active development.