# @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(); 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.