How to write idiomatic assertions with the Bupkis assertion library for TypeScript and JavaScript
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
references/README.mdreferences/api_reference.mdreferences/common_patterns.mdWrite idiomatic, expressive assertions using bupkis' powerful assertion vocabulary to make tests more readable and maintainable.
to satisfy for multiple properties rather than separate assertionsto have property, to be empty, etc.to be an object already implies non-nullPrefer semantic property checks over truthiness checks.
// ❌ 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.
Combine related type checks using to satisfy instead of separate assertions.
// ❌ 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.
Use semantic collection assertions instead of length comparisons.
// ❌ 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.
Don't redundantly check for null when object check already implies it.
// ❌ 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.
Verify object structure with to satisfy instead of multiple property assertions.
// ❌ 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.
Combine multiple checks on a result object with to satisfy instead of separate assertions.
// ❌ 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.
Use positive assertions instead of negated ones when possible.
// ❌ 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.
Use concatenation when making multiple assertions on the same subject.
// ❌ 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.
Use to have properties with an array when checking for multiple properties.
// ❌ 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.
Use expectAsync with 'to reject' for testing promise rejections.
// ❌ 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 errorto satisfy Patterns// Only check specific properties, ignore others
expect(result, 'to satisfy', {
status: 'complete',
// other properties ignored
});
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'),
}),
});
// 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'],
});
// All items must satisfy condition
expect(results, 'to have items satisfying', {
duration: expect.it('to be a number'),
status: 'success',
});
| 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 |
to satisfy shows expected object shape at a glanceexpect() with bupkis assertion vocabularyexpect.it() for nested assertions within to satisfyto have property, not to be empty, to be an objectto satisfy, to deep equal