avajs/ava · error · TypeError
Test & hook titles must be strings
Error message
Test & hook titles must be strings
What it means
AVA validates that any explicitly provided test or hook title is a string. parseTestArgs sets title.isValid only when the value is a string; if a title was supplied but is not a string (number, object, null, template returning non-string, etc.), the constructor throws this TypeError. Titles are used as identifiers for snapshots, --match, and reporting, so they must be strings.
Source
Thrown at lib/runner.js:135
}
// --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)) {
throw new Error(`Duplicate test title: ${title.value}`);
}
const task = {View on GitHub (pinned to bbfd946322)
Solutions
- Make the title a string: wrap with String(...) or use a template literal
- Fix the title variable so it holds a real string at call time
- Move a mistakenly placed implementation argument to its correct position in the call
- Log typeof title just before the call to find where the non-string comes from
Example fix
// before
const id = 42;
test(id, t => { ... });
// after
test(`test ${id}`, t => { ... }); Defensive patterns
Strategy: type-guard
Validate before calling
if (title !== undefined && typeof title !== 'string') {
throw new TypeError('Test titles must be strings');
}
test(title, fn); Type guard
function isValidTitle(title) {
return title === undefined || typeof title === 'string';
} Try / catch
try {
test(title, fn);
} catch (err) {
if (err.message === 'Test & hook titles must be strings') {
test(String(title), fn); // coerce and retry declaration
} else {
throw err;
}
} Prevention
- Wrap dynamic titles in template literals to guarantee string type
- Check typeof of title variables before declaring tests
- Avoid passing numbers, enums, or objects directly as titles
- Enable TypeScript so non-string titles are caught at compile time
When it happens
Trigger: test(42, fn), test({a: 1}, fn), test(null, fn), or calling an assertion-style API like t => {} with a non-string title variable, e.g. test(myTitle, fn) where myTitle is a number or undefined-but-set value.
Common situations: Passing a numeric constant or enum as the title; a variable title that is undefined at runtime but was 'set' via destructuring; migrating code where the title was accidentally dropped into the implementation slot; template literals are fine, but null/undefined variables are not.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Expected an implementation. Use `test.todo()` for tests with
- 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/4488336a226222f2.
Report an issue: GitHub.