The project is using Vue, Vitest and Vue Test Utils. I am new to UI unit testing and I need to test that when one function from composable is called, then the other function should have been called.
My functions look something like this (details omitted):
export function useHelper() {
async function wait(milliseconds: number): Promise<void> {
return await new Promise<void>(resolve => setTimeout(() => resolve(), milliseconds));
}
async function executeInBatches(delay = 0) {
if (delay) {
await wait(delay);
}
}
return {
executeInBatches,
wait,
};
}
My unit test is next:
beforeEach(() => {
vi.useFakeTimers({shouldAdvanceTime: true});
});
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
});
it('should call function wait when delay is provided', async () => {
const composable = useHelper();
const waitSpy = vi.spyOn(composable, 'wait');
const delay = 100;
await executeInBatches(delay);
vi.advanceTimersByTime(delay * 2);
expect(waitSpy).toHaveBeenCalled();
});
I do not understand why waitSpy
does not work. If i do exactly same thing, but spy on window.setTimeout
, which is called inside wait
function, then the timeout spy correctly works.
Working example with spying on setTimeout:
it('should call setTimeout when delay is provided', async () => {
const composable = useHelper();
const timeoutSpy = vi.spyOn(window 'setTimeout')
const delay = 100;
await executeInBatches(delay);
vi.advanceTimersByTime(delay * 2);
expect(timeoutSpy).toHaveBeenCalled();
});
Could you please help, why does spying on composable does not work?
Thanks in advance
This is impossible without changing how JavaScript works. executeInBatches
always refers wait
local function. And if wait
weren't returned, it couldn't be accessed from the outside at all. In order to change this, wait
should have been referred as some object method, e.g. this.wait
, which is unsuitable here because the results of composables are conventionally destructable. This is what happens in the second test, where globalThis.setTimeout
is accessed.
In order to be testable, wait
should be moved to a separate module. It looks like general utility function any way, so it could be reused through the project. This way it can be spied or mocked with vi.mock
.
Otherwise useHelper
needs to be treated a single unit in tests, this is not a problem because Jest/Vitest fake timers allow to effectively control timer-related code.