How to intercept stream writes in a Vitest environment. #6005
-
I have a pattern for intercepting process.stdout and process.stderr which works in a non-vitest environment. However, in a vitest environment my interception of stream What's the correct way to implement this pattern to take account of the Vitest environment and any manipulation it does of process stdout and stderr ? In NodeThis implementation is functional and outputs the intercepted writes as expected when executed using tsx on Node22... type WriteMethod = NodeJS.WriteStream["write"];
async function interceptStreams<StreamName extends string>(
streams: Record<StreamName, NodeJS.WriteStream>,
operation: () => unknown,
) {
const writeFnsByName: Partial<Record<StreamName, WriteMethod>> = {};
const writeArgsByName = Object.fromEntries(
Object.keys(streams).map((name) => [name, []]),
) as unknown as Record<StreamName, Parameters<WriteMethod>[]>;
// start intercepting writes
for (const [name, stream] of Object.entries(streams) as [
StreamName,
NodeJS.WriteStream,
][]) {
// configure interception
const { write } = stream;
const writeArgs: Parameters<WriteMethod>[] = [];
writeFnsByName[name] = write;
writeArgsByName[name] = writeArgs;
// replace write with interceptor
const interceptor = ((...args: Parameters<typeof write>) => {
writeArgs.push(args);
return write.call(stream, ...args);
}) as WriteMethod;
stream.write = interceptor;
}
// execute the operation that we expect to write to streams
await operation();
// stop intercepting writes
for (const [name, stream] of Object.entries<NodeJS.WriteStream>(streams)) {
stream.write = writeFnsByName[name];
}
return writeArgsByName;
}
async function interceptConsoleLog() {
const { stdout, stderr } = process;
const intercepted = await interceptStreams({ stdout, stderr }, () => {
console.log("Good");
console.error("Bad");
});
console.log(JSON.stringify({ intercepted }, null, 2));
}
interceptConsoleLog(); ...runs like...$ npm run env -- tsx test/experiment.ts
> env tsx test/experiment.ts
Good
Bad
{
"intercepted": {
"stdout": [
[
"Good\n",
null
]
],
"stderr": [
[
"Bad\n",
null
]
]
}
} In VitestIf I perform the equivalent steps in Vitest, no write is ever intercepted, as you can see from the inline snapshots... import { describe, test, expect } from "vitest";
type WriteMethod = NodeJS.WriteStream["write"];
async function interceptStreams<StreamName extends string>(
streams: Record<StreamName, NodeJS.WriteStream>,
operation: () => unknown,
) {
const writeFnsByName: Partial<Record<StreamName, WriteMethod>> = {};
const writeArgsByName = Object.fromEntries(
Object.keys(streams).map((name) => [name, []]),
) as unknown as Record<StreamName, Parameters<WriteMethod>[]>;
// start intercepting writes
for (const [name, stream] of Object.entries(streams) as [
StreamName,
NodeJS.WriteStream,
][]) {
// configure interception
const { write } = stream;
const writeArgs: Parameters<WriteMethod>[] = [];
writeFnsByName[name] = write;
writeArgsByName[name] = writeArgs;
// replace write with interceptor
const interceptor = ((...args: Parameters<typeof write>) => {
writeArgs.push(args);
return write.call(stream, ...args);
}) as WriteMethod;
stream.write = interceptor;
}
// execute the operation that we expect to write to streams
await operation();
// stop intercepting writes
for (const [name, stream] of Object.entries<NodeJS.WriteStream>(streams)) {
stream.write = writeFnsByName[name];
}
return writeArgsByName;
}
describe("Logging behaviour", () => {
describe("Default standard out and standard error destinations", () => {
test("Suite can intercept console.log", async () => {
const { stdout, stderr } = process;
const intercepted = await interceptStreams({ stdout, stderr }, () => {
console.log("Good");
console.error("Bad");
});
expect(intercepted.stdout).toMatchInlineSnapshot(`[]`);
expect(intercepted.stderr).toMatchInlineSnapshot(`[]`);
});
});
}); |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
Pass expect(intercepted.stdout).toMatchInlineSnapshot(`
[
[
"Good
",
[Function],
],
]
`);
expect(intercepted.stderr).toMatchInlineSnapshot(`
[
[
"�[31mBad�[39m
",
[Function],
],
]
`); |
Beta Was this translation helpful? Give feedback.
-
Excellent, thanks for your help. That fixed it for me! |
Beta Was this translation helpful? Give feedback.
Pass
--disableConsoleIntercept
or set{ test: { disableConsoleIntercept: true } }
and you'll get expected results: