Specialized assertions for testing function behavior, including exception throwing.
Asserts that the function throws an exception when called. Can optionally specify the type of error and message pattern.
/**
* Asserts that the function throws an exception when called
* @param {Function} [type] - Constructor function the error must be instance of
* @param {string|RegExp} [message] - String or regex the error message must match
* @returns {*} The thrown error object for further inspection
*/
throw(type, message)Alias:
throws()Basic Exception Testing:
const Code = require('@hapi/code');
const expect = Code.expect;
// Function that throws
const throwingFunction = () => {
throw new Error('Something went wrong');
};
// Basic throw assertion
expect(throwingFunction).to.throw();
// Function that doesn't throw
const nonThrowingFunction = () => {
return 'success';
};
expect(nonThrowingFunction).to.not.throw();Error Type Checking:
// Check specific error type
const typeError = () => {
throw new TypeError('Invalid type');
};
const rangeError = () => {
throw new RangeError('Out of range');
};
expect(typeError).to.throw(TypeError);
expect(rangeError).to.throw(RangeError);
expect(typeError).to.throw(Error); // Parent class also works
// Custom error types
class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
}
}
const customErrorFunc = () => {
throw new CustomError('Custom error occurred');
};
expect(customErrorFunc).to.throw(CustomError);Message Validation:
// Exact message matching
const exactMessageFunc = () => {
throw new Error('Exact error message');
};
expect(exactMessageFunc).to.throw('Exact error message');
// Regex message matching
const regexMessageFunc = () => {
throw new Error('File not found: config.json');
};
expect(regexMessageFunc).to.throw(/File not found/);
expect(regexMessageFunc).to.throw(/config\.json$/);
// Type and message combination
expect(regexMessageFunc).to.throw(Error, /File not found/);Working with Thrown Errors:
// Capture and inspect thrown error
const errorFunc = () => {
const err = new Error('Test error');
err.code = 'TEST_ERROR';
err.statusCode = 500;
throw err;
};
const thrownError = expect(errorFunc).to.throw();
// Further assertions on the thrown error
expect(thrownError).to.be.an.instanceof(Error);
expect(thrownError.message).to.equal('Test error');
expect(thrownError.code).to.equal('TEST_ERROR');
expect(thrownError.statusCode).to.equal(500);// Function that throws based on parameters
const validateAge = (age) => {
if (typeof age !== 'number') {
throw new TypeError('Age must be a number');
}
if (age < 0) {
throw new RangeError('Age cannot be negative');
}
if (age > 150) {
throw new RangeError('Age cannot exceed 150');
}
return age;
};
// Test different error conditions
expect(() => validateAge('25')).to.throw(TypeError, /must be a number/);
expect(() => validateAge(-5)).to.throw(RangeError, /cannot be negative/);
expect(() => validateAge(200)).to.throw(RangeError, /cannot exceed 150/);
// Test valid case
expect(() => validateAge(25)).to.not.throw();// For Promise-based errors, use reject() instead
const asyncThrowingFunc = async () => {
throw new Error('Async error');
};
// This won't work - async functions return promises
// expect(asyncThrowingFunc).to.throw(); // Won't catch the error
// Use Promise rejection testing instead
await expect(asyncThrowingFunc()).to.reject(Error, /Async error/);
// Or test the Promise directly
const promise = asyncThrowingFunc();
await expect(promise).to.reject();// Testing multiple possible errors
const complexFunction = (input) => {
if (input === null) {
throw new Error('Input cannot be null');
}
if (typeof input !== 'object') {
throw new TypeError('Input must be an object');
}
if (!input.hasOwnProperty('required')) {
throw new Error('Missing required property');
}
return input.required;
};
// Test each error condition
expect(() => complexFunction(null)).to.throw('Input cannot be null');
expect(() => complexFunction('string')).to.throw(TypeError);
expect(() => complexFunction({})).to.throw(/Missing required/);
// Test success case
expect(() => complexFunction({required: 'value'})).to.not.throw();// Test error recovery mechanisms
class SafeProcessor {
static process(data, options = {}) {
try {
if (!data) {
throw new Error('No data provided');
}
return data.toUpperCase();
} catch (err) {
if (options.throwOnError) {
throw err;
}
return options.defaultValue || '';
}
}
}
// Test throwing behavior
expect(() => SafeProcessor.process(null, {throwOnError: true}))
.to.throw('No data provided');
// Test recovery behavior
expect(() => SafeProcessor.process(null, {throwOnError: false}))
.to.not.throw();
const result = SafeProcessor.process(null, {
throwOnError: false,
defaultValue: 'DEFAULT'
});
expect(result).to.equal('DEFAULT');// Custom error with additional properties
class ValidationError extends Error {
constructor(message, field, value) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
this.timestamp = new Date();
}
}
const validateField = (field, value) => {
if (!value) {
throw new ValidationError(
`${field} is required`,
field,
value
);
}
};
// Test and inspect error properties
const error = expect(() => validateField('email', ''))
.to.throw(ValidationError);
expect(error.field).to.equal('email');
expect(error.value).to.equal('');
expect(error.timestamp).to.be.a.date();
expect(error.message).to.include('email is required');// Special behavior with 'not' flag
const nonThrowingFunc = () => 'success';
const throwingFunc = () => { throw new Error('error'); };
// These work as expected
expect(nonThrowingFunc).to.not.throw();
expect(throwingFunc).to.throw();
// With 'not', cannot specify error type or message
expect(nonThrowingFunc).to.not.throw(); // OK
// expect(nonThrowingFunc).to.not.throw(Error); // Error! Cannot specify args with 'not'
// The negated version returns assertion chain, not error
const result = expect(nonThrowingFunc).to.not.throw();
expect(result).to.be.an.object(); // Returns assertion chain// Common testing patterns
describe('Error handling', () => {
it('should throw on invalid input', () => {
const invalidInputs = [null, undefined, '', 0, false];
invalidInputs.forEach(input => {
expect(() => processInput(input))
.to.throw(Error, /invalid input/i);
});
});
it('should provide detailed error information', () => {
const error = expect(() => processData(badData))
.to.throw(ProcessingError);
expect(error.details).to.exist();
expect(error.inputData).to.equal(badData);
expect(error.suggestion).to.be.a.string();
});
});