Title: test_runner: MockTimers does not support Temporal · Issue #63369 · nodejs/node · GitHub
Open Graph Title: test_runner: MockTimers does not support Temporal · Issue #63369 · nodejs/node
X Title: test_runner: MockTimers does not support Temporal · Issue #63369 · nodejs/node
Description: What is the problem this feature solves? As of Node.js 26, Temporal is enabled by default (#62526), and the platform is in the process of growing Temporal support across its APIs (#57891, #63154, #63312). One gap that doesn't seem to be ...
Open Graph Description: What is the problem this feature solves? As of Node.js 26, Temporal is enabled by default (#62526), and the platform is in the process of growing Temporal support across its APIs (#57891, #63154, #...
X Description: What is the problem this feature solves? As of Node.js 26, Temporal is enabled by default (#62526), and the platform is in the process of growing Temporal support across its APIs (#57891, #63154, #...
Opengraph URL: https://github.com/nodejs/node/issues/63369
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"test_runner: MockTimers does not support Temporal","articleBody":"### What is the problem this feature solves?\n\nAs of Node.js 26, `Temporal` is enabled by default ([#62526](https://github.com/nodejs/node/pull/62526)), and the platform is in the process of growing Temporal support across its APIs ([#57891](https://github.com/nodejs/node/issues/57891), [#63154](https://github.com/nodejs/node/issues/63154), [#63312](https://github.com/nodejs/node/issues/63312)). One gap that doesn't seem to be tracked anywhere yet: `node:test`'s `MockTimers` has no way to control `Temporal.Now`.\n\n`MockTimers.enable({ apis })` accepts `'setTimeout' | 'setInterval' | 'setImmediate' | 'Date'`. Passing `'Temporal'` (or any Temporal-related token) throws `ERR_INVALID_ARG_VALUE`. There is also no documented way to advance / freeze `Temporal.Now.instant()`, `Temporal.Now.zonedDateTimeISO()`, etc. via the mock clock.\n\nConcretely, this means any code that reads \"now\" via `Temporal.Now` instead of `Date.now()` can't be tested deterministically with the built-in test runner. With Temporal now being the recommended way to do date/time work, this is increasingly the common case — code migrating away from `Date` loses its ability to use `MockTimers` for time-based assertions.\n\n### Repro\n\n```js\nimport { mock } from \"node:test\";\n\nmock.timers.enable({ apis: [\"Temporal\"] });\n// TypeError [ERR_INVALID_ARG_VALUE]: The property 'options.apis' must be one of:\n// ['setTimeout', 'setImmediate', 'setInterval', 'Date']\n\nmock.timers.enable({ apis: [\"Date\"], now: 0 });\nconsole.log(new Date().toISOString()); // 1970-01-01T00:00:00.000Z (mocked)\nconsole.log(Temporal.Now.instant().toString()); // current real time (NOT mocked)\n```\n\n### What is the feature you are proposing to solve the problem?\n\nExtend `MockTimers` so that, when `Temporal` is in the `apis` list, the following are tied to the mock clock:\n\n- `Temporal.Now.instant()`\n- `Temporal.Now.zonedDateTimeISO(timeZone?)`\n- `Temporal.Now.plainDateTimeISO(timeZone?)`\n- `Temporal.Now.plainDateISO(timeZone?)`\n- `Temporal.Now.plainTimeISO(timeZone?)`\n- `Temporal.Now.timeZoneId()` (probably leave passthrough; only the clock should be virtual)\n\n`mock.timers.tick(ms)` and `mock.timers.setTime(ms)` should advance Temporal's view of \"now\" the same way they advance `Date.now()`. The natural ergonomic is for `'Date'` and `'Temporal'` to be independently selectable but to share one underlying mock clock (so a test can mock both and have them agree).\n\nThis likely depends on [#63312](https://github.com/nodejs/node/issues/63312) (internal Temporal utils) landing first, since `MockTimers` would need a way to construct `Temporal.Instant`s from the mock epoch ms without round-tripping through `Date`.\n\n### What alternatives have you considered?\n\n- Userland: wrap `Temporal.Now` in a project-level abstraction that reads from a clock the tests can mock. Works but is viral — every consumer has to use the wrapper.\n- Userland: monkey-patch `Temporal.Now` in test setup. Brittle and doesn't compose with `mock.timers`.\n- `@sinonjs/fake-timers` has the same gap upstream, so dropping the built-in runner doesn't help.\n\nFiling per discussion in nodejs/node#57891 (which explicitly scopes out test_runner/generic support) — happy to take this on if there's interest and #63312 looks close to landing.","author":{"url":"https://github.com/ethanresnick","@type":"Person","name":"ethanresnick"},"datePublished":"2026-05-16T20:40:00.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":2},"url":"https://github.com/63369/node/issues/63369"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:7824cdcf-69e9-02a3-6d72-7513b979fed8 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | C3C0:237528:139EBFB:1B3B0B0:6A4CAE86 |
| html-safe-nonce | 7dc2d3fd9cf192543b2a0e276f8c0030de61bc272f3ba04141e8d4fb1a148889 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDM0MwOjIzNzUyODoxMzlFQkZCOjFCM0IwQjA6NkE0Q0FFODYiLCJ2aXNpdG9yX2lkIjoiMjg4NTE0ODgzNTUzNjc0NDA3MCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | d5cc3f6dfa5facccedab853d5d69d5fb6c2f03345725379c9a78028b0fd36558 |
| hovercard-subject-tag | issue:4461209243 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/63369/issue_layout |
| twitter:image | https://opengraph.githubassets.com/be83a4aae3d03d8f52e1cf69abb5223f2640931d4e8e31476624359b8f076aa4/nodejs/node/issues/63369 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/be83a4aae3d03d8f52e1cf69abb5223f2640931d4e8e31476624359b8f076aa4/nodejs/node/issues/63369 |
| og:image:alt | What is the problem this feature solves? As of Node.js 26, Temporal is enabled by default (#62526), and the platform is in the process of growing Temporal support across its APIs (#57891, #63154, #... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | ethanresnick |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3d11bb817438277de2a940854450e83a7d32b6aeb5014e9e6b00a6423900251c |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | ae90d426644ca15e89bacceb72e51f4e9dbf85f7 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width