Skip to content

perf: avoid copying mock history in call-count assertions - #62

Open
exo-egor wants to merge 2 commits into
ExodusOSS:mainfrom
exo-egor:exo-egor/perf/mock-call-count
Open

perf: avoid copying mock history in call-count assertions#62
exo-egor wants to merge 2 commits into
ExodusOSS:mainfrom
exo-egor:exo-egor/perf/mock-call-count

Conversation

@exo-egor

@exo-egor exo-egor commented Sep 8, 2026

Copy link
Copy Markdown

Count-only mock assertions currently rebuild the entire argument history to read its length. Use the underlying mock context's counter through a WeakMap, retaining the existing fallback for Jest-compatible mocks. This makes successful count lookups constant-time and adds no fields to mock objects. The registry lives in its own internal module so the bundler does not mistake mock setup for usage of expect.

A synthetic loop of 5,000 mock calls, each followed by toHaveBeenCalledTimes, improved from 185–200 ms to 20–21 ms on Node 22.22.0 / macOS (about 9× for that loop). These are the medians of seven samples after three warm-up loops, in two separate processes per revision. Whole-suite speedup has not been measured.

Validation:

  • Full Node, pure-JavaScript, and bundled suites pass: 147, 146, and 70 suites respectively (FORCE_COLOR=1 for the existing color-dependent snapshots).
  • The full 70-suite Hermes run passes, including the mock matcher regressions.
  • The new regression fails on the original implementation, reporting 10 call-history reads instead of zero. It also covers bound mocks, clear/reset, aliases, external-compatible mocks, completed/thrown calls, and failed assertions.
  • Formatting and ESLint pass for all changed files. Full lint reports an existing @exodus/import/no-extraneous-dependencies error in tests/jest/setup.cjs:9, reproduced on untouched upstream main.
Microbenchmark reproduction

Run this from either checkout with EXODUS_TEST_ENGINE=node:test node --input-type=module:

import { performance } from 'node:perf_hooks'
import { jestfn } from './src/jest.fn.js'
import { expect } from './src/expect.cjs'

function run() {
  const fn = jestfn()
  const start = performance.now()
  for (let i = 1; i <= 5000; i++) {
    fn(i)
    expect(fn).toHaveBeenCalledTimes(i)
  }
  return performance.now() - start
}
for (let i = 0; i < 3; i++) run()
const samples = Array.from({ length: 7 }, run).sort((a, b) => a - b)
console.log({ samples, median: samples[3] })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant