Files
tstest/ts_tapbundle/readme.md

634 lines
15 KiB
Markdown

# @git.zone/tstest/tapbundle
> 🧪 Core TAP testing framework with enhanced assertions and lifecycle hooks
## Installation
```bash
# tapbundle is typically included as part of @git.zone/tstest
pnpm install --save-dev @git.zone/tstest
```
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## Overview
`@git.zone/tstest/tapbundle` is the core testing framework module that provides the TAP (Test Anything Protocol) implementation for tstest. It offers a comprehensive API for writing and organizing tests with support for lifecycle hooks, test suites, enhanced assertions with diff generation, and flexible test configuration.
## Key Features
- 🎯 **TAP Protocol Compliant** - Full TAP version 13 support
- 🔍 **Enhanced Assertions** - Built on smartexpect with automatic diff generation
- 🏗️ **Test Suites** - Organize tests with `describe()` blocks
- 🔄 **Lifecycle Hooks** - beforeEach/afterEach at suite and global levels
- 🏷️ **Test Tagging** - Filter tests by tags for selective execution
-**Parallel Testing** - Run tests concurrently with `testParallel()`
- 🔁 **Automatic Retries** - Configure retry logic for flaky tests
- ⏱️ **Timeout Control** - Set timeouts at global, file, or test level
- 🎨 **Fluent API** - Chain test configurations with builder pattern
- 📊 **Protocol Events** - Real-time test execution events
## Basic Usage
### Simple Test File
```typescript
import { tap, expect } from '@git.zone/tstest/tapbundle';
tap.test('should add numbers correctly', async () => {
const result = 2 + 2;
expect(result).toEqual(4);
});
export default tap.start();
```
### Using Test Suites
```typescript
import { tap, expect } from '@git.zone/tstest/tapbundle';
tap.describe('Calculator', () => {
tap.beforeEach(async (tapTools) => {
// Setup before each test in this suite
});
tap.test('should add', async () => {
expect(2 + 2).toEqual(4);
});
tap.test('should subtract', async () => {
expect(5 - 3).toEqual(2);
});
tap.afterEach(async (tapTools) => {
// Cleanup after each test in this suite
});
});
export default tap.start();
```
## API Reference
### Main Test Methods
#### `tap.test(description, testFunction)`
Define a standard test that runs sequentially.
```typescript
tap.test('should validate user input', async () => {
// test code
});
```
#### `tap.testParallel(description, testFunction)`
Define a test that runs in parallel with other parallel tests.
```typescript
tap.testParallel('should fetch user data', async () => {
// test code
});
```
**Note:** The `tap.parallel().test()` fluent API is now the recommended way to define parallel tests (see Fluent API section below).
#### `tap.parallel()`
Returns a fluent test builder configured for parallel execution.
```typescript
tap.parallel().test('should fetch data', async () => {
// Parallel test
});
// With full configuration
tap.parallel()
.tags('api')
.retry(2)
.test('configured parallel test', async () => {});
```
#### `tap.describe(description, suiteFunction)`
Create a test suite to group related tests.
```typescript
tap.describe('User Authentication', () => {
tap.test('should login', async () => { });
tap.test('should logout', async () => { });
});
```
### Test Modes
#### Skip Tests
```typescript
tap.skip.test('not ready yet', async () => {
// This test will be skipped
});
```
#### Only Mode
```typescript
tap.only.test('focus on this test', async () => {
// Only tests marked with 'only' will run
});
```
#### Todo Tests
```typescript
tap.todo.test('implement feature X');
```
### Fluent Test Builder
Chain test configurations for expressive test definitions:
```typescript
tap
.tags('integration', 'database')
.priority('high')
.retry(3)
.timeout(5000)
.test('should handle database connection', async () => {
// test with configured settings
});
```
#### Parallel Tests with Fluent API
Use `tap.parallel()` to create parallel tests with fluent configuration:
```typescript
// Simple parallel test
tap.parallel().test('fetches user data', async () => {
// Runs in parallel with other parallel tests
});
// Parallel test with full configuration
tap
.parallel()
.tags('api', 'integration')
.retry(2)
.timeout(5000)
.test('should fetch data concurrently', async () => {
// Configured parallel test
});
```
**Note:** `tap.parallel().test()` is the recommended way to define parallel tests. The older `tap.testParallel()` method is still supported for backward compatibility.
### Lifecycle Hooks
#### Suite-Level Hooks
```typescript
tap.describe('Database Tests', () => {
tap.beforeAll(async (tapTools) => {
// Runs once before all tests in this suite
await initializeDatabaseConnection();
});
tap.beforeEach(async (tapTools) => {
// Runs before each test in this suite
await clearTestData();
});
tap.test('test 1', async () => { });
tap.test('test 2', async () => { });
tap.afterEach(async (tapTools) => {
// Runs after each test in this suite
});
tap.afterAll(async (tapTools) => {
// Runs once after all tests in this suite
await closeDatabaseConnection();
});
});
```
#### Global Hooks
```typescript
tap.settings({
beforeAll: async () => {
// Runs once before all tests
},
afterAll: async () => {
// Runs once after all tests
},
beforeEach: async (testName) => {
// Runs before every test
},
afterEach: async (testName, passed) => {
// Runs after every test
}
});
```
### Global Settings
Configure test behavior at the file level:
```typescript
tap.settings({
timeout: 10000, // Default timeout for all tests
retries: 2, // Retry failed tests
retryDelay: 1000, // Delay between retries
bail: false, // Stop on first failure
suppressConsole: false, // Hide console output
verboseErrors: true, // Show full stack traces
showTestDuration: true, // Display test durations
maxConcurrency: 4, // Max parallel tests
});
```
### Enhanced Assertions
The `expect` function is an enhanced wrapper around [@push.rocks/smartexpect](https://code.foss.global/push.rocks/smartexpect) that automatically generates diffs for failed assertions.
```typescript
import { expect } from '@git.zone/tstest/tapbundle';
tap.test('should compare objects', async () => {
const actual = { name: 'John', age: 30 };
const expected = { name: 'John', age: 31 };
// Will show a detailed diff of the differences
expect(actual).toEqual(expected);
});
```
#### Available Assertions
```typescript
// Equality
expect(value).toEqual(expected);
expect(value).toBe(expected);
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
// Type checks
expect(value).toBeType('string');
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
// Arrays
expect(array).toContain(item);
// Exceptions
expect(fn).toThrow();
expect(fn).toThrow('error message');
// Async
await expect(promise).toResolve();
await expect(promise).toReject();
```
### Test Tagging and Filtering
Tag tests for selective execution:
```typescript
// Define tests with tags
tap.tags('integration', 'slow').test('complex test', async () => {
// test code
});
tap.tags('unit').test('fast test', async () => {
// test code
});
```
Filter tests by setting the environment variable:
```bash
TSTEST_FILTER_TAGS=unit tstest test/mytest.node.ts
```
### TapTools
Each test receives a `tapTools` instance with utilities:
#### Test Control Methods
```typescript
tap.test('test control examples', async (tapTools) => {
// Skip this test
tapTools.skip('reason');
// Conditionally skip
tapTools.skipIf(condition, 'reason');
// Mark test as skipped before execution
tapTools.markAsSkipped('reason');
// Mark as todo
tapTools.todo('not implemented');
// Allow test to fail without marking suite as failed
tapTools.allowFailure();
// Configure retries
tapTools.retry(3);
// Set timeout
tapTools.timeout(5000);
});
```
#### Utility Methods
```typescript
tap.test('utility examples', async (tapTools) => {
// Delay execution
await tapTools.delayFor(1000); // Wait 1 second
await tapTools.delayForRandom(500, 1500); // Random delay
// Colored console output
tapTools.coloredString('✓ Success', 'green');
tapTools.coloredString('✗ Error', 'red');
});
```
#### Context and Data Sharing
```typescript
tap.test('first test', async (tapTools) => {
// Store data in context
tapTools.context.set('userId', '12345');
// Store in testData property
tapTools.testData = { username: 'alice' };
});
tap.test('second test', async (tapTools) => {
// Retrieve from context
const userId = tapTools.context.get('userId');
// Check existence
if (tapTools.context.has('userId')) {
// Use data
}
// Clear context
tapTools.context.clear();
});
```
#### Fixtures
```typescript
// Define a fixture globally (outside tests)
import { TapTools } from '@git.zone/tstest/tapbundle';
TapTools.defineFixture('database', async () => {
const db = await createTestDatabase();
return {
value: db,
cleanup: async () => await db.close()
};
});
// Use fixtures in tests
tap.test('database test', async (tapTools) => {
const db = await tapTools.fixture('database');
// Use db...
// Cleanup happens automatically
});
```
#### Factory Pattern
```typescript
// Define a factory
TapTools.defineFixture('user', async () => {
return {
value: null, // Not used for factories
factory: async (data) => {
return await createUser(data);
},
cleanup: async (user) => await user.delete()
};
});
// Use factory in tests
tap.test('user test', async (tapTools) => {
const user = await tapTools.factory('user').create({ name: 'Alice' });
// Create multiple
const users = await tapTools.factory('user').createMany([
{ name: 'Alice' },
{ name: 'Bob' }
]);
// Cleanup happens automatically
});
```
#### Snapshot Testing
```typescript
tap.test('snapshot test', async (tapTools) => {
const result = { name: 'Alice', age: 30 };
// Compare with stored snapshot
await tapTools.matchSnapshot(result);
// Named snapshots
await tapTools.matchSnapshot(result, 'user-data');
});
```
To update snapshots, run with:
```bash
UPDATE_SNAPSHOTS=true tstest test/mytest.ts
```
## Advanced Features
### Pre-Tasks and Post-Tasks
Run setup and teardown tasks before/after all tests:
```typescript
tap.preTask('setup database', async () => {
// Runs before any tests
await initializeDatabase();
});
tap.test('first test', async () => {
// Database is ready
});
tap.test('second test', async () => {
// Tests run...
});
tap.postTask('cleanup database', async () => {
// Runs after all tests complete
await cleanupDatabase();
});
```
**Note:** Post tasks run after all tests but before the global `afterAll` hook.
### Test Priority
Organize tests by priority level:
```typescript
tap.priority('high').test('critical test', async () => { });
tap.priority('medium').test('normal test', async () => { });
tap.priority('low').test('optional test', async () => { });
```
### Nested Suites
Create deeply nested test organization:
```typescript
tap.describe('API', () => {
tap.describe('Users', () => {
tap.describe('GET /users', () => {
tap.test('should return all users', async () => { });
});
});
});
```
### Protocol Events
Access real-time test events for custom tooling:
```typescript
import { setProtocolEmitter } from '@git.zone/tstest/tapbundle';
// Get access to protocol emitter for custom event handling
// Events: test:started, test:completed, assertion:failed, suite:started, suite:completed
```
### Additional Tap Methods
#### Configuration and Inspection
```typescript
// Get current test settings
const settings = tap.getSettings();
console.log(settings.timeout, settings.retries);
// Explicitly fail a test
tap.test('validation test', async () => {
if (invalidCondition) {
tap.fail('Custom failure message');
}
});
```
#### Advanced Control
```typescript
// Force stop test execution
tap.stopForcefully(exitCode, immediate);
// Handle thrown errors (internal use)
tap.threw(error);
```
#### Parallel Test Variants
In addition to `tap.parallel().test()`, skip/only/todo modes also support parallel execution:
```typescript
// Skip parallel test
tap.skip.testParallel('not ready', async () => {});
// Only run this parallel test
tap.only.testParallel('focus here', async () => {});
// Todo parallel test
tap.todo.testParallel('implement later');
```
**Note:** Using `tap.parallel()` fluent API is recommended over these direct methods.
## Best Practices
1. **Always export `tap.start()`** at the end of test files:
```typescript
export default tap.start();
```
2. **Use descriptive test names** that explain what is being tested:
```typescript
tap.test('should return 404 when user does not exist', async () => { });
```
3. **Group related tests** with `describe()` blocks:
```typescript
tap.describe('User validation', () => {
// All user validation tests
});
```
4. **Leverage lifecycle hooks** to reduce duplication:
```typescript
tap.beforeEach(async () => {
// Common setup
});
```
5. **Tag tests appropriately** for flexible test execution:
```typescript
tap.tags('integration', 'database').test('...', async () => { });
```
## TypeScript Support
tapbundle is written in TypeScript and provides full type definitions. The `Tap` class accepts a generic type for shared context:
```typescript
interface MyTestContext {
db: DatabaseConnection;
user: User;
}
const tap = new Tap<MyTestContext>();
tap.test('should use context', async (tapTools) => {
// tapTools is typed with MyTestContext
});
```
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](../license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.