Skip to content

Instantly share code, notes, and snippets.

@joemaller
Created January 26, 2024 23:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joemaller/f9171aa19a187f59f406ef1ffe87d9ac to your computer and use it in GitHub Desktop.
Save joemaller/f9171aa19a187f59f406ef1ffe87d9ac to your computer and use it in GitHub Desktop.
Mock child_process.exec in module in Vitest
import { exec } from "node:child_process";
export function callExec() {
return new Promise((resolve, reject) => {
exec(`ls -la`, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
if (stdout) {
resolve(stdout);
}
});
// uncomment this so the promise resolves after mocking exec
// setTimeout(() => resolve("1 second later"), 1000);
});
}
import { expect, test, vi } from "vitest";
import { exec } from "node:child_process";
vi.mock("node:child_process");
import { callExec } from "./call-exec.js";
test("Mock child_process.exec", async () => {
callExec();
expect(exec).toHaveBeenCalledTimes(1);
expect(actual).toBe("1 second later");
});

I thought this was difficiult to get working, but in retrospect, vitest was probably fine and I just missed a detail about how the code was structured.

After imported a few things from Vitest, exec is imported from the node:child_process module. Then, Vitest just mocks the entire parent module. The order actually doesn't matter since Vitest will hoist the mock call in a way that assures it works with the imported module.

Once that was working, my test didn't actually work -- it was spinning unitl it was cut off by Vitest's 5 second timeout. That was happening because the call-exec module wraps the exec call in a Promise which resolves or rejects from the exec callback, the wrapping promise could never be resolved by the mock, since the resolve and reject functions were inherited from the containing scope -- which the mocked function couldn't know about.

This was a simplified reduction of a quirk that I didn't see right away and ended up wasting too much time on. I refactored the testable parts out of the promise and moved on to other stuff.

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