634 lines
15 KiB
Markdown
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.
|