avajs/ava · error · AssertionError

`t.like()` selector must not contain circular references

Error message

`t.like()` selector must not contain circular references

What it means

This AVA assertion error is thrown by t.like() when the selector object contains circular references. t.like() walks the selector via selectComparable() to pick the comparable subset of actual; a circular selector makes that walk impossible, so the library detects it (CIRCULAR_SELECTOR sentinel) and throws a fixed usage message instead of letting the walk blow the stack. The selector must be an acyclic plain object.

Source

Thrown at lib/assert.js:376

			return pass();
		});

		this.like = withSkip((actual, selector, message) => {
			assertMessage(message, 't.like()');

			if (!isLikeSelector(selector)) {
				throw fail(new AssertionError('`t.like()` selector must be a non-empty object', {
					assertion: 't.like()',
					formattedDetails: [formatWithLabel('Called with:', selector)],
				}));
			}

			let comparable;
			try {
				comparable = selectComparable(actual, selector);
			} catch (error) {
				if (error === CIRCULAR_SELECTOR) {
					throw fail(new AssertionError('`t.like()` selector must not contain circular references', {
						assertion: 't.like()',
						formattedDetails: [formatWithLabel('Called with:', selector)],
					}));
				}

				throw error;
			}

			const result = concordance.compare(comparable, selector, concordanceOptions);
			if (result.pass) {
				return pass();
			}

			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)],

View on GitHub (pinned to bbfd946322)

Solutions

  1. Inspect the selector printed under 'Called with:' and remove or restructure the self-referencing property.
  2. If you need to assert on a circular actual value, pass only the non-cyclic subset as the selector.
  3. Restructure the selector as a fresh literal containing only the fields you want matched.

Example fix

// before
const selector = { name: 'x' };
selector.parent = selector; // circular
t.like(value, selector);
// after
const selector = { name: 'x' };
t.like(value, selector);
Defensive patterns

Strategy: validation

Validate before calling

function hasCircular(obj, seen = new Set()) {
  if (obj === null || typeof obj !== 'object') return false;
  if (seen.has(obj)) return true;
  seen.add(obj);
  return Object.values(obj).some(v => hasCircular(v, seen));
}
// before calling: if (hasCircular(selector)) throw new Error('selector is circular');

Type guard

const isAcyclicSelector = (s) => typeof s === 'object' && s !== null && !hasCircular(s);

Try / catch

try {
  t.like(actual, selector);
} catch (err) {
  if (/circular references/.test(err.message)) {
    t.fail('selector passed to t.like() is circular: ' + describe(selector));
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling t.like(actual, selector) where the selector (or a nested value inside it) references itself directly or indirectly, e.g. const sel = {a: 1}; sel.self = sel; t.like(obj, sel).

Common situations: Building the selector by mutation where a property is accidentally assigned back the selector itself; reusing an object that another object points back to; copying config objects that contain parent links.

Related errors


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