avajs/ava · error · TypeError

Expected an implementation. Use `test.todo()` for tests with

Error message

Expected an implementation. Use `test.todo()` for tests without an implementation.

What it means

AVA throws this TypeError when a non-todo test or hook is declared without a function implementation. The chain callback expects typeof implementation === 'function'; anything else (undefined, a string, an object) fails. AVA points you to test.todo() as the correct way to declare a placeholder test with no implementation.

Source

Thrown at lib/runner.js:131

				}

				if (!this.registerUniqueTitle(title.value)) {
					throw new Error(`Duplicate test title: ${title.value}`);
				}

				// --match selects TODO tests.
				metadata.selected &&= isTitleMatch(title.value, this.matchPatterns);

				this.tasks.todo.push({title: title.value, metadata});
				this.emit('stateChange', {
					type: 'declared-test',
					title: title.value,
					knownFailing: false,
					todo: true,
				});
			} else {
				if (typeof implementation !== 'function') {
					throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
				}

				if (title.isSet && !title.isValid) {
					throw new TypeError('Test & hook titles must be strings');
				}

				let fallbackTitle = title.value;
				if (title.isEmpty) {
					if (metadata.type === 'test') {
						throw new TypeError('Tests must have a title');
					} else if (metadata.always) {
						fallbackTitle = `${metadata.type}.always hook`;
					} else {
						fallbackTitle = `${metadata.type} hook`;
					}
				}

				if (metadata.type === 'test' && !this.registerUniqueTitle(title.value)) {

View on GitHub (pinned to bbfd946322)

Solutions

  1. Pass an implementation function as the callback: test('title', async t => { ... })
  2. If the test is intentionally unimplemented, use test.todo('title') instead
  3. Check that the implementation variable/import actually resolves to a function (fix broken imports/circular deps)
  4. If the test should not run yet, use test.skip('title', t => { ... }) which still requires a function

Example fix

// before
test('parses config');

// after
test('parses config', t => {
	t.deepEqual(parseConfig('a=1'), {a: 1});
});
// or, if unimplemented:
test.todo('parses config');
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof implementation !== 'function') {
	throw new TypeError('test() requires an implementation function; use test.todo() if unimplemented');
}
test('title', implementation);

Type guard

function hasImplementation(args) {
	return args.length >= 2 && typeof args[args.length - 1] === 'function';
}

Try / catch

try {
	test(title, impl);
} catch (err) {
	if (err.message.includes('Expected an implementation')) {
		test.todo(title); // degrade to a todo placeholder
	} else {
		throw err;
	}
}

Prevention

When it happens

Trigger: test('title') with no second/third argument; passing a non-function (e.g. an async arrow assigned later, an undefined import, a string) as the implementation: test('x', myImpl) where myImpl is undefined due to a bad import or circular dependency.

Common situations: Forgot the callback entirely when scaffolding tests; a misnamed or wrongly-defaulted import resolves to undefined; an import cycle yields undefined at declaration time; converting test.skip back to test but leaving no callback.

Related errors


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