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
- Pass an implementation function as the callback: test('title', async t => { ... })
- If the test is intentionally unimplemented, use test.todo('title') instead
- Check that the implementation variable/import actually resolves to a function (fix broken imports/circular deps)
- 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
- Always pass a callback to test(); use test.todo() for placeholders
- Verify imports used as implementations actually resolve (watch for circular dependencies)
- When converting test.skip() back, restore the full implementation
- Use an editor snippet/template for test declarations so the callback is never omitted
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
- Test & hook titles must be strings
- Tests must have a title
- Duplicate test title: ${title.value}
- Expected an implementation.
- `t.try()` titles must be strings
AI-assisted analysis of avajs/ava@bbfd946322 (2026-09-02).
Data as JSON: /api/errors/4c87ff45c00f0410.
Report an issue: GitHub.