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

promise-testing.mddocs/

Promise Testing

Asynchronous testing capabilities for Promise rejection scenarios.

Capabilities

reject() Method

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

Advanced Usage Patterns

Testing Async Functions

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

API Error Handling

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

Promise Chain Error Testing

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

Timeout and Race Conditions

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

Error Recovery and Fallbacks

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

Multiple Promise Testing

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

Negation with reject()

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

Integration Patterns

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