avajs/ava · error · AssertionError

`t.throws()` must be called with a function

Error message

`t.throws()` must be called with a function

What it means

AVA throws this fixed usage error when t.throws() is invoked with a first argument that is not a function. t.throws() synchronously invokes its argument to observe a throw, so anything other than a callable is improper usage; the error is flagged with improperUsage so AVA can point at a mistake in the test rather than a failure of the code under test.

Source

Thrown at lib/assert.js:407

			const actualDescriptor = result.actual ?? concordance.describe(comparable, concordanceOptions);
			const expectedDescriptor = result.expected ?? concordance.describe(selector, concordanceOptions);
			throw fail(new AssertionError(message, {
				assertion: 't.like()',
				formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
			}));
		});

		this.throws = withSkip((...args) => {
			// Since arrow functions do not support 'arguments', we are using rest
			// operator, so we can determine the total number of arguments passed
			// to the function.
			let [fn, expectations, message] = args;

			assertMessage(message, 't.throws()');

			if (typeof fn !== 'function') {
				throw fail(new AssertionError('`t.throws()` must be called with a function', {
					assertion: 't.throws()',
					improperUsage: {assertion: 'throws'},
					formattedDetails: [formatWithLabel('Called with:', fn)],
				}));
			}

			try {
				expectations = validateExpectations('t.throws()', expectations, args.length, experiments);
			} catch (error) {
				throw fail(error);
			}

			let retval;
			let threw = false;
			let actual = null;
			try {
				retval = fn();
				if (isPromise(retval)) {

View on GitHub (pinned to bbfd946322)

Solutions

  1. Wrap the call in a function: t.throws(() => risky()).
  2. If the code is asynchronous, switch to t.throwsAsync(() => riskyAsync()).
  3. Check that the variable holding the function is actually defined and is a function.

Example fix

// before
t.throws(readConfig('missing.json'));
// after
t.throws(() => readConfig('missing.json'));
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== 'function') throw new TypeError('t.throws() expects a function; got ' + typeof fn);

Type guard

const isCallable = (v) => typeof v === 'function';
if (!isCallable(fn)) { /* fix before asserting */ }

Try / catch

try {
  t.throws(fn, expectations);
} catch (err) {
  if (/must be called with a function/.test(err.message)) {
    t.fail('Wrap the call in a thunk: t.throws(() => ...)');
  } else { throw err; }
}

Prevention

When it happens

Trigger: t.throws(undefined), t.throws(await asyncFn()), t.throws(promise) — passing the result of an async call, a promise, or forgetting to pass a thunk, e.g. t.throws(fs.readFileSync('nope')) instead of () => fs.readFileSync('nope').

Common situations: Calling the function eagerly instead of wrapping it in an arrow function; migrating from t.throwsAsync and passing a promise; typo'd variable that is undefined at call time.

Related errors


AI-assisted analysis of avajs/ava@bbfd946322 (2026-09-02). Data as JSON: /api/errors/e3929dad7fe4c93f. Report an issue: GitHub.