Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:01:33 +08:00
commit 0cc3c9a83a
9 changed files with 2628 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
---
name: bupkis-assertion-patterns
description: How to write idiomatic assertions with the Bupkis assertion library for TypeScript and JavaScript
---
# Bupkis Assertion Patterns
Write idiomatic, expressive assertions using bupkis' powerful assertion vocabulary to make tests more readable and maintainable.
## When to Use
- Writing tests with the bupkis assertion library
- Checking properties, types, or structure of objects
- Verifying arrays or collections
- Want clearer, more expressive test failures
## When NOT to Use
- When a simple, direct assertion is clearer (don't over-complicate)
- When the pattern doesn't improve readability
## Core Principles
1. **Use semantic assertions**: Choose assertions that express intent, not implementation
2. **Combine related checks**: Use `to satisfy` for multiple properties rather than separate assertions
3. **Leverage bupkis vocabulary**: Use built-in assertions like `to have property`, `to be empty`, etc.
4. **Let assertions imply related checks**: e.g., `to be an object` already implies non-null
## Patterns
### 1. Property Existence
**Prefer semantic property checks over truthiness checks.**
```typescript
// ❌ DON'T - unclear intent, indirect check
expect('filesCompleted' in state, 'to be truthy');
// ✅ DO - clear, semantic assertion
expect(state, 'to have property', 'filesCompleted');
```
**Why:** `to have property` expresses the intent clearly and provides better error messages.
---
### 2. Type Checking Multiple Properties
**Combine related type checks using `to satisfy` instead of separate assertions.**
```typescript
// ❌ DON'T - repetitive, verbose
expect(typeof state.filesCompleted, 'to equal', 'number');
expect(typeof state.suitesCompleted, 'to equal', 'number');
expect(typeof state.tasksCompleted, 'to equal', 'number');
// ✅ DO - concise, shows structure at a glance
expect(state, 'to satisfy', {
filesCompleted: expect.it('to be a number'),
suitesCompleted: expect.it('to be a number'),
tasksCompleted: expect.it('to be a number'),
});
```
**Why:** `to satisfy` lets you verify multiple properties in one assertion, showing the expected structure clearly. Better error messages show exactly which properties failed.
---
### 3. Non-Empty Collections
**Use semantic collection assertions instead of length comparisons.**
```typescript
// ❌ DON'T - indirect, requires mental math
expect(result.files.length, 'to be greater than', 0);
// ✅ DO - direct, semantic
expect(result.files, 'not to be empty');
```
**Why:** `not to be empty` directly expresses the intent. Works for arrays, strings, objects, Maps, Sets, etc.
---
### 4. Object and Null Checks
**Don't redundantly check for null when object check already implies it.**
```typescript
// ❌ DON'T - redundant null check
expect(config, 'to be an object');
expect(config, 'not to be null');
// ✅ DO - object check implies non-null
expect(config, 'to be an object'); // already implies non-null
```
**Why:** In bupkis, `to be an object` already ensures the value is not null. Redundant checks add noise.
---
### 5. Object Structure Verification
**Verify object structure with `to satisfy` instead of multiple property assertions.**
```typescript
// ❌ DON'T - fragmented, hard to see expected structure
expect(config, 'to be an object');
expect(config.iterations, 'to equal', 500);
expect(config.reporters[0], 'to equal', 'json');
expect(config.reporters.length, 'to equal', 1);
// ✅ DO - clear, declarative structure check
expect(config, 'to satisfy', {
iterations: 500,
reporters: expect.it('to deep equal', ['json']),
});
```
**Why:** `to satisfy` lets you declaratively specify the expected structure. Combines type check and property validation in one assertion. Shows the expected shape at a glance.
---
### 5b. Multiple Conditions on the Same Result Object
**Combine multiple checks on a result object with `to satisfy` instead of separate assertions.**
```typescript
// ❌ DON'T - multiple separate assertions
expect(result.exitCode, 'to equal', 0);
expect(result.stdout, 'to match', /No historical data/);
expect(result.stderr, 'not to match', /toLocaleDateString is not a function/);
// ✅ DO - single to satisfy assertion
expect(result, 'to satisfy', {
exitCode: 0,
stdout: expect.it('to match', /No historical data/),
stderr: expect.it('not to match', /toLocaleDateString is not a function/),
});
```
**Why:** Groups all related checks on the same object. Shows the expected result state clearly. Easier to maintain - add/remove checks in one place. Better error messages show exactly which property failed.
---
### 6. Defined Value Checks
**Use positive assertions instead of negated ones when possible.**
```typescript
// ❌ DON'T - negated assertion
expect(result, 'not to be undefined');
// ✅ DO - positive, semantic
expect(result, 'to be defined');
```
**Why:** `to be defined` is clearer and more idiomatic than negating undefined. It's a positive assertion that directly expresses intent.
---
### 7. Chaining Assertions with 'and'
**Use concatenation when making multiple assertions on the same subject.**
```typescript
// ❌ DON'T - separate assertions
expect(config, 'to be an object'); // implies non-null
expect(config, 'to have property', 'reporters');
// ✅ DO - chain with 'and'
expect(config, 'to be an object', 'and', 'to have property', 'reporters');
```
**Why:** Chaining assertions with `'and'` keeps related checks together in a single statement. More concise and shows that you're checking multiple aspects of the same value. Better error messages that show which part of the chain failed.
---
### 8. Multiple Property Checks with Array
**Use `to have properties` with an array when checking for multiple properties.**
```typescript
// ❌ DON'T - chain multiple property checks
expect(
config,
'to be an object',
'and',
'to have property',
'outputDir',
'and',
'to have property',
'reporters',
);
// ✅ DO - use 'to have properties' with array
expect(config, 'to have properties', ['outputDir', 'reporters']);
```
**Why:** `to have properties` with an array is specifically designed for checking multiple properties at once. More concise than chaining. Shows all required properties clearly in one place. Better error messages that list all missing properties.
---
### 9. Promise Rejection Checks
**Use `expectAsync` with `'to reject'` for testing promise rejections.**
```typescript
// ❌ DON'T - wishy-washy try/catch
try {
await configManager.load('nonexistent.config.json');
expect(true, 'to be truthy'); // Maybe it works?
} catch (error) {
expect(error, 'to be an', Error); // Or maybe it throws?
expect((error as Error).message, 'not to be empty');
}
// ✅ DO - explicit promise rejection check
await expectAsync(configManager.load('nonexistent.config.json'), 'to reject');
```
**Why:** Makes the contract explicit - either it should reject or it shouldn't. No ambiguity. `expectAsync` is specifically designed for promise-based assertions. `'to reject'` clearly expresses that rejection is the expected behavior.
**Related assertions:**
- `'to reject'` - promise should be rejected
- `'to reject with error satisfying'` - promise should reject with specific error
---
## Advanced `to satisfy` Patterns
### Partial Object Matching
```typescript
// Only check specific properties, ignore others
expect(result, 'to satisfy', {
status: 'complete',
// other properties ignored
});
```
### Nested Structure
```typescript
expect(benchmark, 'to satisfy', {
name: expect.it('to be a string'),
results: expect.it('to satisfy', {
mean: expect.it('to be a number'),
median: expect.it('to be a number'),
}),
});
```
### Array Element Values
```typescript
// Check specific array values within to satisfy
expect(config, 'to satisfy', {
iterations: 100,
reporters: ['human'], // checks first element matches
});
// Or check multiple elements
expect(config, 'to satisfy', {
tags: ['performance', 'critical'],
});
```
### Arrays with Patterns
```typescript
// All items must satisfy condition
expect(results, 'to have items satisfying', {
duration: expect.it('to be a number'),
status: 'success',
});
```
## Quick Reference
| Instead of... | Use... |
| -------------------------------------- | ------------------------------------------------------ |
| `'prop' in obj, 'to be truthy'` | `obj, 'to have property', 'prop'` |
| `typeof x.prop, 'to equal', 'number'` | `x, 'to satisfy', {prop: expect.it('to be a number')}` |
| `arr.length, 'to be greater than', 0` | `arr, 'not to be empty'` |
| `result, 'not to be undefined'` | `result, 'to be defined'` |
| Separate expect() on same subject | Chain with `'and'`: `expect(x, 'a', 'and', 'b')` |
| Multiple 'to have property' assertions | `to have properties`, `['prop1', 'prop2']` |
| try/catch for promise rejection | `await expectAsync(promise, 'to reject')` |
| Multiple assertions on the same object | `to satisfy` with object structure |
| Separate object + null checks | Just `to be an object` |
## Benefits
1. **Clearer intent**: Semantic assertions express what you're testing, not how
2. **Better error messages**: bupkis shows exactly what failed in structural checks
3. **More maintainable**: Related checks grouped together, easier to update
4. **Less code**: Combine multiple assertions into expressive structure checks
5. **Discover structure**: `to satisfy` shows expected object shape at a glance
## Tools Used
- `expect()` with bupkis assertion vocabulary
- `expect.it()` for nested assertions within `to satisfy`
- Semantic assertions: `to have property`, `not to be empty`, `to be an object`
- Structural assertions: `to satisfy`, `to deep equal`

View File

@@ -0,0 +1,69 @@
# Bupkis References
This directory contains reference documentation for the Bupkis assertion library.
## Available References
### 1. api_reference.md - Complete API Documentation
**Comprehensive reference for all built-in Bupkis assertions** organized by category:
- **Core Concepts** - Natural language assertions, negation, concatenation, embeddable assertions
- **Primitive Assertions** - Type checks (string, number, boolean, null, undefined, etc.)
- **Numeric Assertions** - Number comparisons, ranges, special values (NaN, Infinity)
- **String & Pattern Assertions** - String matching, RegExp, substring operations
- **Collection Assertions** - Arrays, Maps, Sets, WeakMap, WeakSet operations
- **Object Assertions** - Property checks, object matching, `to satisfy` patterns
- **Function Assertions** - Function type checks, arity, throw behavior
- **Equality & Comparison Assertions** - Deep equality, instance checks
- **Error Assertions** - Error type and message validation
- **Date & Time Assertions** - Date comparisons, durations, weekday/weekend checks
- **Promise Assertions** - Async resolution and rejection testing
- **Other Assertions** - Truthy, falsy, defined checks
**When to use:** When you need to look up the exact syntax for an assertion, understand what parameters it accepts, or see all available aliases.
### 2. common_patterns.md - Practical Usage Patterns
**Real-world examples and best practices** for common testing scenarios:
- **API Response Validation** - REST API, pagination, error responses
- **Configuration Validation** - App config, environment-specific config, feature flags
- **Error Testing Patterns** - Standard errors, custom errors, promise rejections
- **Async Operation Patterns** - Promise resolution, async functions, race conditions
- **Complex Nested Structures** - Deep object validation, arrays of objects, optional properties
- **Test Data Validation** - Factory data, fixtures, mock data consistency
- **Type Safety Patterns** - Discriminated unions, generic validation, branded types
- **Real-World Scenarios** - Database queries, forms, file system, HTTP headers, events
**When to use:** When implementing tests and need examples of idiomatic Bupkis patterns for common use cases.
## How These References Are Used
When Claude loads this skill and you ask about Bupkis assertions, it will:
1. **Load SKILL.md first** (always in context) - Provides the core patterns and workflow
2. **Load references as needed** - Fetches specific reference files based on your question:
- Questions about "what assertions are available" → `api_reference.md`
- Questions about "how to test API responses" → `common_patterns.md`
- Questions about specific assertion syntax → `api_reference.md`
This three-level progressive disclosure keeps context usage efficient while ensuring comprehensive information is available when needed.
## Reference File Organization
Both reference files use:
- Clear table of contents for quick navigation
- Consistent formatting (heading hierarchy, code blocks)
- Success ✓ and failure ✗ examples for each assertion
- Inline comments explaining key concepts
- Cross-references between related sections
## Extending These References
If you discover new patterns or need to document additional use cases:
1. **Add patterns to common_patterns.md** - Keep examples practical and well-commented
2. **Update api_reference.md** - If Bupkis adds new assertions
3. **Keep files under 10k words each** - Split into subtopics if growing too large

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,603 @@
# Common Bupkis Patterns
Practical patterns and real-world examples for using Bupkis assertions effectively.
## Table of Contents
- [API Response Validation](#api-response-validation)
- [Configuration Validation](#configuration-validation)
- [Error Testing Patterns](#error-testing-patterns)
- [Async Operation Patterns](#async-operation-patterns)
- [Complex Nested Structures](#complex-nested-structures)
- [Test Data Validation](#test-data-validation)
- [Type Safety Patterns](#type-safety-patterns)
---
## API Response Validation
### REST API Success Response
```typescript
const response = await fetch('/api/users/1');
const data = await response.json();
// Validate response structure
expect(data, 'to satisfy', {
id: expect.it('to be a number'),
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
name: expect.it('to be a string', 'and', 'not to be empty'),
createdAt: expect.it('to be a valid date'),
roles: expect.it('to be an array', 'and', 'not to be empty'),
});
```
### Paginated API Response
```typescript
const response = await fetchPaginatedUsers({ page: 1, limit: 10 });
expect(response, 'to satisfy', {
data: expect.it('to be an array', 'and', 'to have length', 10),
meta: {
currentPage: 1,
totalPages: expect.it('to be a positive integer'),
totalItems: expect.it('to be a positive integer'),
hasNextPage: expect.it('to be a boolean'),
},
});
// Validate each user in the array
expect(response.data, 'to have items satisfying', {
id: expect.it('to be a positive integer'),
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
name: expect.it('to be a string'),
});
```
### Error Response Validation
```typescript
const response = await fetchWithError('/api/invalid');
expect(response, 'to satisfy', {
error: {
code: expect.it('to be a string'),
message: expect.it('to be a string', 'and', 'not to be empty'),
statusCode: expect.it('to be between', 400, 599),
},
});
```
---
## Configuration Validation
### Application Config
```typescript
const config = loadAppConfig();
expect(config, 'to satisfy', {
port: expect.it('to be between', 1024, 65535),
host: expect.it('to be a string'),
database: {
url: /^postgresql:\/\//,
poolSize: expect.it('to be a positive integer'),
},
logging: {
level: expect.it('to be one of', ['debug', 'info', 'warn', 'error']),
format: expect.it('to be one of', ['json', 'text']),
},
});
```
### Environment-Specific Config
```typescript
const prodConfig = loadConfig('production');
expect(prodConfig, 'to satisfy', {
env: 'production',
debug: false,
apiUrl: /^https:\/\//, // Must be HTTPS in production
rateLimiting: {
enabled: true,
maxRequests: expect.it('to be a positive integer'),
},
});
```
### Feature Flags
```typescript
const featureFlags = getFeatureFlags();
expect(featureFlags, 'to be an object', 'and', 'not to be empty');
expect(featureFlags, 'to satisfy', {
newDashboard: expect.it('to be a boolean'),
betaFeatures: expect.it('to be a boolean'),
experimentalApi: expect.it('to be a boolean'),
});
```
---
## Error Testing Patterns
### Standard Error Handling
```typescript
// Function that should throw
expect(() => parseJSON('invalid json'), 'to throw a', SyntaxError);
// With message matching
expect(
() => parseJSON('invalid json'),
'to throw a',
SyntaxError,
'satisfying',
{ message: /Unexpected token/ },
);
```
### Custom Error Properties
```typescript
expect(
() => {
const err = new Error('Database connection failed');
err.code = 'ECONNREFUSED';
err.port = 5432;
throw err;
},
'to throw an error satisfying',
{
message: /connection failed/,
code: 'ECONNREFUSED',
port: 5432,
},
);
```
### Promise Rejection
```typescript
// Simple rejection
await expectAsync(fetchData('/api/invalid'), 'to reject');
// With error type
await expectAsync(fetchData('/api/invalid'), 'to reject with a', HttpError);
// With error properties
await expectAsync(
fetchData('/api/invalid'),
'to reject with error satisfying',
{
statusCode: 404,
message: /not found/,
},
);
```
### Try-Catch Alternative
```typescript
// ❌ DON'T use try-catch for expected errors
try {
await riskyOperation();
expect(true, 'to be truthy'); // Maybe it works?
} catch (error) {
expect(error, 'to be an', Error); // Or maybe it throws?
}
// ✅ DO use expectAsync with explicit contract
await expectAsync(riskyOperation(), 'to reject with a', ValidationError);
```
---
## Async Operation Patterns
### Promise Resolution
```typescript
// Basic resolution
await expectAsync(fetchUser(123), 'to resolve');
// With value validation
await expectAsync(fetchUser(123), 'to resolve with value satisfying', {
id: 123,
name: expect.it('to be a string'),
email: /^[^\s@]+@/,
});
```
### Async Function Testing
```typescript
async function processData(input) {
// ... processing
return result;
}
await expectAsync(
async () => processData({ valid: true }),
'to resolve with value satisfying',
{ success: true },
);
await expectAsync(
async () => processData({ invalid: true }),
'to reject with error satisfying',
{ code: 'INVALID_INPUT' },
);
```
### Race Conditions
```typescript
// Ensure operation completes within timeout
const promise = Promise.race([slowOperation(), timeout(5000)]);
await expectAsync(promise, 'to resolve with value satisfying', {
completed: true,
});
```
---
## Complex Nested Structures
### Deep Object Validation
```typescript
const benchmark = {
name: 'Array.sort performance',
date: '2024-01-15',
results: {
samples: [1.2, 1.3, 1.1, 1.4, 1.2],
statistics: {
mean: 1.24,
median: 1.2,
stdDev: 0.11,
min: 1.1,
max: 1.4,
},
},
metadata: {
platform: 'Node.js v20.10.0',
arch: 'x64',
},
};
expect(benchmark, 'to satisfy', {
name: expect.it('to be a string', 'and', 'not to be empty'),
date: /^\d{4}-\d{2}-\d{2}$/,
results: {
samples: expect.it('to be an array', 'and', 'to be non-empty'),
statistics: {
mean: expect.it('to be a number', 'and', 'to be positive'),
median: expect.it('to be a number', 'and', 'to be positive'),
stdDev: expect.it('to be a number'),
min: expect.it('to be a number'),
max: expect.it('to be a number'),
},
},
metadata: expect.it('to be an object'),
});
```
### Array of Complex Objects
```typescript
const transactions = [
{ id: 1, amount: 100.5, currency: 'USD', status: 'completed' },
{ id: 2, amount: 250.0, currency: 'EUR', status: 'pending' },
{ id: 3, amount: 75.25, currency: 'GBP', status: 'completed' },
];
// Validate array structure
expect(transactions, 'to be an array', 'and', 'not to be empty');
// Validate each transaction
expect(transactions, 'to have items satisfying', {
id: expect.it('to be a positive integer'),
amount: expect.it('to be a positive number'),
currency: expect.it('to be one of', ['USD', 'EUR', 'GBP']),
status: expect.it('to be one of', ['pending', 'completed', 'failed']),
});
```
### Optional Properties
```typescript
const user = {
id: 123,
name: 'Alice',
email: 'alice@example.com',
// phone is optional
};
// Use `to satisfy` to ignore missing optional properties
expect(user, 'to satisfy', {
id: expect.it('to be a positive integer'),
name: expect.it('to be a string'),
email: /^[^\s@]+@/,
// phone not required, will pass if missing
});
// Explicitly check for optional property if present
if ('phone' in user) {
expect(user.phone, 'to match', /^\+?\d{10,15}$/);
}
```
---
## Test Data Validation
### Factory-Generated Data
```typescript
const user = UserFactory.create();
// Validate factory output
expect(user, 'to satisfy', {
id: expect.it('to be a positive integer'),
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
createdAt: expect.it('to be a Date'),
roles: expect.it('to be an array', 'and', 'not to be empty'),
});
```
### Fixture Data
```typescript
const fixtures = loadFixtures('users.json');
expect(fixtures, 'to be an array', 'and', 'to be non-empty');
// Validate all fixtures
expect(fixtures, 'to have items satisfying', {
id: expect.it('to be a number'),
email: expect.it('to be a string'),
name: expect.it('to be a string'),
});
```
### Mock Data Consistency
```typescript
const mockResponse = {
users: [
/* ... */
],
meta: { total: 50 },
};
// Ensure mock data is consistent
expect(
mockResponse.users.length,
'to be less than or equal to',
mockResponse.meta.total,
);
```
---
## Type Safety Patterns
### Discriminated Unions
```typescript
type SuccessResult = { success: true; data: any };
type ErrorResult = { success: false; error: string };
type Result = SuccessResult | ErrorResult;
const result: Result = processOperation();
if (result.success) {
expect(result, 'to satisfy', {
success: true,
data: expect.it('to be defined'),
});
} else {
expect(result, 'to satisfy', {
success: false,
error: expect.it('to be a string', 'and', 'not to be empty'),
});
}
```
### Generic Type Validation
```typescript
function validateResponse<T>(
response: unknown,
shape: Record<string, any>,
): asserts response is T {
expect(response, 'to satisfy', shape);
}
// Usage
const response = await fetchData();
validateResponse<User>(response, {
id: expect.it('to be a number'),
email: expect.it('to be a string'),
});
// TypeScript now knows response is User
console.log(response.email);
```
### Branded Types
```typescript
type UserId = number & { __brand: 'UserId' };
function assertUserId(value: number): asserts value is UserId {
expect(value, 'to be a positive integer');
}
const id: number = 123;
assertUserId(id);
// id is now UserId
```
---
## Real-World Scenarios
### Database Query Results
```typescript
const users = await db.query('SELECT * FROM users WHERE active = true');
expect(users, 'to be an array');
expect(users, 'to have items satisfying', {
id: expect.it('to be a positive integer'),
email: expect.it('to be a string'),
active: true,
created_at: expect.it('to be a Date'),
});
```
### Form Validation
```typescript
const formData = {
username: 'alice123',
email: 'alice@example.com',
password: 'securePassword123!',
age: 25,
};
expect(formData, 'to satisfy', {
username: expect.it('to be a string', 'and', 'to have length between', 3, 20),
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
password: expect.it('to be a string', 'and', 'to have length at least', 8),
age: expect.it('to be between', 13, 120),
});
```
### File System Operations
```typescript
const stats = await fs.stat('package.json');
expect(stats, 'to satisfy', {
size: expect.it('to be a positive number'),
isFile: expect.it('to be a function'),
isDirectory: expect.it('to be a function'),
mtime: expect.it('to be a Date'),
});
expect(stats.isFile(), 'to be true');
```
### HTTP Headers Validation
```typescript
const headers = response.headers;
expect(headers, 'to satisfy', {
'content-type': /^application\/json/,
'cache-control': expect.it('to be a string'),
'x-request-id': expect.it('to be a string', 'and', 'not to be empty'),
});
// Check CORS headers
expect(headers, 'to have property', 'access-control-allow-origin');
```
### Event Emission Validation
```typescript
const emitter = new EventEmitter();
const events: any[] = [];
emitter.on('data', (data) => events.push(data));
emitter.emit('data', { id: 1, value: 'test' });
emitter.emit('data', { id: 2, value: 'test2' });
expect(events, 'to have length', 2);
expect(events, 'to have items satisfying', {
id: expect.it('to be a positive integer'),
value: expect.it('to be a string'),
});
```
---
## Anti-Patterns to Avoid
### ❌ Don't: Overly Specific Assertions
```typescript
// Too brittle - breaks if order changes
expect(users, 'to deep equal', [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
```
### ✅ Do: Use Partial Matching
```typescript
// More flexible - only checks what matters
expect(users, 'to have items satisfying', {
id: expect.it('to be a number'),
name: expect.it('to be a string'),
});
```
### ❌ Don't: Redundant Checks
```typescript
// Redundant - `to be an object` already implies non-null
expect(config, 'to be an object');
expect(config, 'not to be null'); // Unnecessary!
```
### ✅ Do: Rely on Implied Checks
```typescript
// Object check already ensures non-null
expect(config, 'to be an object');
```
### ❌ Don't: Multiple Separate Assertions
```typescript
// Fragmented - hard to see the expected structure
expect(result.exitCode, 'to equal', 0);
expect(result.stdout, 'to match', /success/);
expect(result.stderr, 'to be empty');
```
### ✅ Do: Use `to satisfy` for Related Properties
```typescript
// Clear - shows expected structure at a glance
expect(result, 'to satisfy', {
exitCode: 0,
stdout: /success/,
stderr: expect.it('to be empty'),
});
```
---
## Tips for Writing Better Assertions
1. **Use `to satisfy` for complex objects** - It's more maintainable than multiple assertions
2. **Leverage `expect.it()` for nested validation** - Embed assertions within object patterns
3. **Use RegExp for flexible string matching** - More robust than exact string comparisons
4. **Chain related assertions with 'and'** - Keeps related checks together
5. **Prefer semantic assertions** - Use `to have property` instead of `'key' in obj`
6. **Use `to have properties` for multiple keys** - More concise than chaining
7. **Let assertions imply related checks** - Don't redundantly check for null after object check
8. **Use `expectAsync` for promises** - Makes contract explicit: resolve or reject