emberjs/ember.js · error · Error

Attempted to assert destroyables destroyed, but you did not

Error message

Attempted to assert destroyables destroyed, but you did not start a destroyable test. Did you forget to call `enableDestroyableTracking()`

What it means

`assertDestroyablesDestroyed()` verifies that all destroyables created during a tracking session were destroyed, but only works when tracking was started via `enableDestroyableTracking()`. If the `isTesting` flag is false when it is called, the library throws because there is nothing to assert against. This guards against asserting in production or outside a tracking session.

Source

Thrown at packages/@glimmer/destroyable/index.ts:295

if (DEBUG) {
  let isTesting = false;

  enableDestroyableTracking = () => {
    if (isTesting) {
      // Reset destroyable meta just in case, before throwing the error
      DESTROYABLE_META = new WeakMap();
      throw new Error(
        'Attempted to start destroyable testing, but you did not end the previous destroyable test. Did you forget to call `assertDestroyablesDestroyed()`'
      );
    }

    isTesting = true;
    DESTROYABLE_META = new Map();
  };

  assertDestroyablesDestroyed = () => {
    if (!isTesting) {
      throw new Error(
        'Attempted to assert destroyables destroyed, but you did not start a destroyable test. Did you forget to call `enableDestroyableTracking()`'
      );
    }

    isTesting = false;

    let map = DESTROYABLE_META as Map<Destroyable, DestroyableMeta<Destroyable>>;
    DESTROYABLE_META = new WeakMap();

    let undestroyed: object[] = [];

    map.forEach((meta) => {
      if (meta.state !== DESTROYED_STATE) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
        undestroyed.push(meta.source!);
      }
    });

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Call `enableDestroyableTracking()` in beforeEach before asserting in afterEach
  2. Ensure `assertDestroyablesDestroyed()` is only called once per tracking session
  3. Guard the afterEach with a flag or check that tracking was actually started in that test
  4. Initialize tracking in a shared test helper so setup/teardown order is guaranteed

Example fix

// before
afterEach(() => { assertDestroyablesDestroyed(); });
// after
beforeEach(() => { enableDestroyableTracking(); });
afterEach(() => { assertDestroyablesDestroyed(); });
Defensive patterns

Strategy: validation

Validate before calling

let trackingStarted = false;
afterEach(function () {
  if (trackingStarted) assertDestroyablesDestroyed();
  trackingStarted = false;
});

Type guard

const canAssertDestroyed = () => trackingStarted === true;

Try / catch

try { assertDestroyablesDestroyed(); } catch (e) { if (/did not start a destroyable test/.test(e.message)) { enableDestroyableTracking(); assertDestroyablesDestroyed(); } else throw e; }

Prevention

When it happens

Trigger: Calling `assertDestroyablesDestroyed()` before ever calling `enableDestroyableTracking()`, or after a previous `assertDestroyablesDestroyed()` already reset `isTesting` to false (double assertion).

Common situations: afterEach hooks that call the assertion unconditionally even for tests that never enabled tracking; assertion order bugs where teardown runs before setup.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/4ad71522472daa15. Report an issue: GitHub.