or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertion-modifiers.mdcontent-testing.mdcore-functions.mdfunction-testing.mdindex.mdnumeric-comparisons.mdpattern-validation.mdpromise-testing.mdsettings.mdstring-assertions.mdtype-assertions.mdvalue-assertions.md
tile.json

function-testing.mddocs/

Function Testing

Specialized assertions for testing function behavior, including exception throwing.

Capabilities

throw() Method

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);

Advanced Usage Patterns

Testing Function Parameters

// 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();

Async Function Error Handling

// 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();

Complex Error Scenarios

// 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();

Error Boundaries and Recovery

// 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');

Testing Error Properties

// 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');

Negation with throw()

// 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

Integration with Test Frameworks

// 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();
    });
});