{"record":{"id":"6f54a6fdc28eb69e","repo":"ReactiveX/rxjs","slug":"marblecontext-settimeout-callback-must-be-a-funct","errorCode":null,"errorMessage":"MarbleContext.setTimeout: callback must be a function","messagePattern":"MarbleContext\\.setTimeout: callback must be a function","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/rxjs/src/testing/index.ts","lineNumber":35,"sourceCode":"const originalClearTimeout = globalThis.clearTimeout;\nconst originalSetInterval = globalThis.setInterval;\nconst originalClearInterval = globalThis.clearInterval;\n\nconst flushMicrotasks = Promise.resolve();\n\nexport class FakeTimers {\n  #timerId = 0;\n  #timerQueue: TimerQueueItem[] = [];\n\n  #shouldUseNodeTimeout = typeof globalThis.setTimeout(() => {}) === 'object';\n\n  #setTimeout: typeof globalThis.setTimeout = (() => {\n    const patched = (callback: TimerHandler, delay = 0, ...args: any[]): any => {\n      const id = ++this.#timerId;\n      const time = this.#now + delay;\n\n      if (typeof callback !== 'function') {\n        throw new TypeError('MarbleContext.setTimeout: callback must be a function');\n      }\n\n      const item: TimerQueueItem = { id, callback: callback as (...args: any[]) => void, delay, time, type: 'timeout', args };\n      this.#addTimer(item);\n\n      if (this.#shouldUseNodeTimeout) {\n        let ref = false;\n        const nodeTimeout = {\n          ref: () => {\n            ref = true;\n            return nodeTimeout;\n          },\n          unref: () => {\n            ref = false;\n            return nodeTimeout;\n          },\n          hasRef: () => {\n            return ref;","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/ReactiveX/rxjs/blob/54796b38a57e6309f9861e174737479bb3f63f61/packages/rxjs/src/testing/index.ts#L17-L53","documentation":"The TestScheduler's MarbleContext patches setTimeout to run inside the virtual clock; the callback must be a real function. Passing a string of code (legacy setTimeout('code()') semantics), an object, or undefined throws a TypeError rather than being queued.","triggerScenarios":"Within a TestScheduler.run(...) callback calling setTimeout('doThing()', 100), or passing a non-function callback such as setTimeout(null, 10) or forwarding a possibly-undefined handler.","commonSituations":"Legacy browser-style code using string callbacks, or application code under test that conditionally calls setTimeout(cb) where cb can be undefined due to an optional callback bug.","solutions":["Always pass a function: setTimeout(() => doThing(), 100)","Fix optional-callback bugs so undefined is never forwarded to setTimeout","Assert callback type in test setup if the code under test receives timers from untrusted input"],"exampleFix":"// before\nsetTimeout('console.log(1)', 100);\n// after\nsetTimeout(() => console.log(1), 100);","handlingStrategy":"type-guard","validationCode":"if (typeof callback !== 'function') throw new TypeError('callback must be a function');\nsetTimeout(callback, delay);","typeGuard":"const isFn = (cb: unknown): cb is (...args: any[]) => void => typeof cb === 'function';","tryCatchPattern":null,"preventionTips":["Never pass strings to setTimeout inside TestScheduler.run","Type callback params as functions, not TimerHandler, in code under test","Guard optional callbacks before forwarding to timers"],"tags":["test-scheduler","marble-testing","settimeout","callback-validation"],"backgroundTag":"invalid-callback-type","analyzedSha":"54796b38a57e6309f9861e174737479bb3f63f61","analyzedAt":"2026-08-28T10:21:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}