javascripttypescriptmockingvitestspyon

Mocking side effect function with Vitest


Not sure how I can mock a side-effect function with Vitest in Typescript Node.js project, Vitest 0.33.0. Consider this simplified example:

// dummy.ts
export function parent(): string {
  return `foo${child()}`;
}

export function child(): string {
  console.log('calling actual child');
  return 'bar';
}
// dummy.test.ts
describe('parent', () => {
  it('should return foobar', () => {
    const spy = vi.spyOn(dummy, 'child').mockReturnValueOnce('baz'); // also tried mockImplementationOnce
    expect(dummy.parent()).eq('foobaz'); // fails, got foobar
    expect(spy).toHaveBeenCalled(); // fails, actual child function is called
  });
});

In this simplified example, actual child() function is called instead of the spy.

The following did not work either:

vi.mock('./dummy.js', async () => {
  const actual = await vi.importActual<typeof import('./dummy.js')>('./dummy.js');
  return {
    ...actual,
    child: () => 'baz',
  };
});

describe('parent', () => {
  it('should return foobar', () => {
    expect(dummy.parent()).eq('foobaz'); // fails, got foobar
  });
});

Solution

  • Internal references inside the dummy.ts module cannot mocked.

    A simple alternative would be to move your child function into a different module and mock that module separately.

    // dummy-parent.ts
    import { child } from './dummy-child';
    
    export function parent(): string {
      return `foo${child()}`;
    }
    
    // dummy-child.ts
    export function child(): string {
      console.log('calling actual child');
      return 'bar';
    }
    
    import { dummy } from './dummy-parent';
    
    vi.mock('./dummy-child', () => {
      return {
        child: () => 'baz',
      };
    });
    
    describe('parent', () => {
      it('should return foobar', () => {
        expect(dummy.parent()).eq('foobaz');
      });
    });