feat(tapbundle): Add global postTask (teardown) and suite lifecycle hooks (beforeAll/afterAll) to tapbundle
This commit is contained in:
@@ -91,6 +91,24 @@ tap.testParallel('should fetch user data', async () => {
|
||||
});
|
||||
```
|
||||
|
||||
**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.
|
||||
@@ -141,22 +159,56 @@ tap
|
||||
});
|
||||
```
|
||||
|
||||
#### 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.test('test 1', async () => { });
|
||||
tap.test('test 2', async () => { });
|
||||
tap.afterAll(async (tapTools) => {
|
||||
// Runs once after all tests in this suite
|
||||
await closeDatabaseConnection();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -267,38 +319,169 @@ TSTEST_FILTER_TAGS=unit tstest test/mytest.node.ts
|
||||
|
||||
Each test receives a `tapTools` instance with utilities:
|
||||
|
||||
#### Test Control Methods
|
||||
|
||||
```typescript
|
||||
tap.test('should have utilities', async (tapTools) => {
|
||||
// Mark test as skipped
|
||||
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);
|
||||
|
||||
// Log test output
|
||||
tapTools.log('debug message');
|
||||
// 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
|
||||
### Pre-Tasks and Post-Tasks
|
||||
|
||||
Run setup tasks before any tests execute:
|
||||
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:
|
||||
@@ -334,6 +517,50 @@ import { setProtocolEmitter } from '@git.zone/tstest/tapbundle';
|
||||
// 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:
|
||||
|
||||
Reference in New Issue
Block a user