BDD assertion library designed to work seamlessly with the hapi ecosystem
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Asynchronous testing capabilities for Promise rejection scenarios.
Asserts that the Promise rejects with an exception. Can optionally specify the type of error and message pattern.
/**
* Asserts that the Promise rejects with an exception
* @param {Function} [type] - Constructor function the error must be instance of
* @param {string|RegExp} [message] - String or regex the error message must match
* @returns {Promise<*>} Promise resolving to the rejected error object
*/
reject(type, message)Alias: rejects()
Basic Promise Rejection Testing:
const Code = require('@hapi/code');
const expect = Code.expect;
// Promise that rejects
const rejectingPromise = Promise.reject(new Error('Something went wrong'));
// Basic rejection assertion
await expect(rejectingPromise).to.reject();
// Promise that resolves
const resolvingPromise = Promise.resolve('success');
await expect(resolvingPromise).to.not.reject();Error Type Checking:
// Check specific error type
const typeErrorPromise = Promise.reject(new TypeError('Invalid type'));
const rangeErrorPromise = Promise.reject(new RangeError('Out of range'));
await expect(typeErrorPromise).to.reject(TypeError);
await expect(rangeErrorPromise).to.reject(RangeError);
await expect(typeErrorPromise).to.reject(Error); // Parent class works
// Custom error types
class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
}
}
const customErrorPromise = Promise.reject(new CustomError('Custom error'));
await expect(customErrorPromise).to.reject(CustomError);Message Validation:
// Exact message matching
const exactMessagePromise = Promise.reject(new Error('Exact error message'));
await expect(exactMessagePromise).to.reject('Exact error message');
// Regex message matching
const regexMessagePromise = Promise.reject(new Error('File not found: config.json'));
await expect(regexMessagePromise).to.reject(/File not found/);
await expect(regexMessagePromise).to.reject(/config\.json$/);
// Type and message combination
await expect(regexMessagePromise).to.reject(Error, /File not found/);Working with Rejected Errors:
// Capture and inspect rejected error
const errorPromise = Promise.reject((() => {
const err = new Error('Test error');
err.code = 'TEST_ERROR';
err.statusCode = 500;
return err;
})());
const rejectedError = await expect(errorPromise).to.reject();
// Further assertions on the rejected error
expect(rejectedError).to.be.an.instanceof(Error);
expect(rejectedError.message).to.equal('Test error');
expect(rejectedError.code).to.equal('TEST_ERROR');
expect(rejectedError.statusCode).to.equal(500);// Async function that throws
const asyncThrowingFunc = async (shouldThrow) => {
if (shouldThrow) {
throw new Error('Async operation failed');
}
return 'success';
};
// Test rejection
await expect(asyncThrowingFunc(true)).to.reject(Error, /operation failed/);
// Test success
await expect(asyncThrowingFunc(false)).to.not.reject();// Simulated API calls
const apiCall = async (endpoint) => {
if (endpoint === '/error') {
const error = new Error('API Error');
error.status = 500;
error.response = {code: 'INTERNAL_ERROR'};
throw error;
}
if (endpoint === '/notfound') {
const error = new Error('Not Found');
error.status = 404;
throw error;
}
return {data: 'success'};
};
// Test different error scenarios
const error500 = await expect(apiCall('/error')).to.reject();
expect(error500.status).to.equal(500);
expect(error500.response.code).to.equal('INTERNAL_ERROR');
const error404 = await expect(apiCall('/notfound')).to.reject();
expect(error404.status).to.equal(404);
// Test success case
await expect(apiCall('/success')).to.not.reject();// Complex promise chain
const processData = async (data) => {
return Promise.resolve(data)
.then(d => {
if (!d) throw new Error('No data provided');
return d;
})
.then(d => {
if (typeof d !== 'string') throw new TypeError('Data must be string');
return d.toUpperCase();
})
.then(d => {
if (d.length < 3) throw new RangeError('Data too short');
return d;
});
};
// Test different error conditions in the chain
await expect(processData(null)).to.reject(Error, /No data/);
await expect(processData(123)).to.reject(TypeError, /must be string/);
await expect(processData('hi')).to.reject(RangeError, /too short/);
// Test success
const result = await processData('hello world');
expect(result).to.equal('HELLO WORLD');// Promise that times out
const timeoutPromise = (ms, shouldReject = false) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(new Error('Operation timed out'));
} else {
resolve('completed');
}
}, ms);
});
};
// Test timeout rejection
await expect(timeoutPromise(100, true)).to.reject(/timed out/);
// Test successful completion
await expect(timeoutPromise(1, false)).to.not.reject();
// Race condition testing
const fastPromise = Promise.resolve('fast');
const slowRejectingPromise = timeoutPromise(100, true);
const winner = await Promise.race([fastPromise, slowRejectingPromise]);
expect(winner).to.equal('fast');// Promise with fallback handling
const withFallback = async (primaryOperation, fallbackValue) => {
try {
return await primaryOperation();
} catch (error) {
if (error.code === 'RECOVERABLE') {
return fallbackValue;
}
throw error; // Re-throw non-recoverable errors
}
};
const recoverableError = () => {
const err = new Error('Recoverable error');
err.code = 'RECOVERABLE';
throw err;
};
const nonRecoverableError = () => {
const err = new Error('Fatal error');
err.code = 'FATAL';
throw err;
};
// Test recovery
const result1 = await withFallback(recoverableError, 'fallback');
expect(result1).to.equal('fallback');
// Test non-recoverable error
await expect(withFallback(nonRecoverableError, 'fallback'))
.to.reject(Error, /Fatal error/);// Testing Promise.all error handling
const promises = [
Promise.resolve('success1'),
Promise.reject(new Error('failure')),
Promise.resolve('success2')
];
await expect(Promise.all(promises)).to.reject(Error, /failure/);
// Testing Promise.allSettled
const settledResults = await Promise.allSettled(promises);
expect(settledResults[0].status).to.equal('fulfilled');
expect(settledResults[1].status).to.equal('rejected');
expect(settledResults[2].status).to.equal('fulfilled');
expect(settledResults[1].reason).to.be.an.instanceof(Error);
expect(settledResults[1].reason.message).to.equal('failure');// Special behavior with 'not' flag
const resolvingPromise = Promise.resolve('success');
const rejectingPromise = Promise.reject(new Error('error'));
// These work as expected
await expect(resolvingPromise).to.not.reject();
await expect(rejectingPromise).to.reject();
// With 'not', cannot specify error type or message
await expect(resolvingPromise).to.not.reject(); // OK
// await expect(resolvingPromise).to.not.reject(Error); // Error! Cannot specify args with 'not'
// The negated version returns null instead of error
const result = await expect(resolvingPromise).to.not.reject();
expect(result).to.be.null();// Database operation testing
const DatabaseError = class extends Error {
constructor(message, query) {
super(message);
this.name = 'DatabaseError';
this.query = query;
}
};
const dbQuery = async (sql) => {
if (sql.includes('DROP')) {
throw new DatabaseError('DROP operations not allowed', sql);
}
if (sql.includes('INVALID')) {
throw new DatabaseError('Invalid SQL syntax', sql);
}
return {rows: [], count: 0};
};
// Test database error scenarios
const dropError = await expect(dbQuery('DROP TABLE users'))
.to.reject(DatabaseError, /not allowed/);
expect(dropError.query).to.include('DROP TABLE');
await expect(dbQuery('SELECT * FROM users')).to.not.reject();
// File system operation testing
const fs = require('fs').promises;
// Test file not found
await expect(fs.readFile('/nonexistent/file.txt')).to.reject();
// Test permission denied (if running test with restricted permissions)
// await expect(fs.writeFile('/root/test.txt', 'data')).to.reject(/permission denied/i);