Vitest에서 같은 파일 내의 function의 mocking을 시험해 보았다.

이번에 vitest를 이용해 테스트 코드를 작성 중 같은 파일 내에 있는 function을 spyon을 이용해 mocking하고 싶은 케이스가 생겨 시험해 보았습니다.
2024.04.14

이번에 vitest를 이용해 테스트 코드를 작성 중 같은 파일 내에 있는 function을 spyon을 이용해 mocking하고 싶은 케이스가 생겨 시험해 보았습니다.

결론

결론 적으로는 같은 파일 내의 function을 spyon을 이용해 mocking하는 것은 불가능 한 것 같습니다.

Beware that it is not possible to mock calls to methods that are called inside other methods of the same file.

vitest관련 문서를 찾아보면 위처럼 불가능하다고 설명하고 있습니다.

확인

그럼 직접 코드를 작성해 확인해 보겠습니다.

sample.ts

export const buildTitle = (): string => {
 return "title"
}
export const buildDescription = (): string => {
 return "description"
}

export const buildTitleAndDescription = (): string => {
 const title = buildTitle()
 const description = buildDescription()

 return `${title},${description}`;
}

sample.test.ts

import * as sample from './sample';
import { vi } from 'vitest';

describe('Test', () => {
  test('Test buildTitleAndDescription()', () => {
    const mockedBuildTitle = vi.spyOn(sample, 'buildTitle').mockReturnValue('mocked-title');
    const mockedBuildDescription = vi.spyOn(sample, 'buildDescription').mockReturnValue('mocked-description');

    const result = sample.buildTitleAndDescription();
    expect(mockedBuildTitle).toHaveBeenCalled();
    expect(mockedBuildDescription).toHaveBeenCalled();
    expect(result).toBe('mocked-title,mocked-description');
  });
});

일반적으로 다른 파일로 나누어 작성했을 때는 문제 없는 코드 입니다만, 같은 파일 내에서 작성하게 되면 에러를 만나게 됩니다.

해결방법

This is the intended behaviour. It is usually a sign of bad code when mocking is involved in such a manner. Consider refactoring your code into multiple files or improving your application architecture by using techniques such as dependency injection.

해결 방법보다는 회피 방법에 가깝다고 생각하지만, 가장 심플한 방법은 2가지 정도가 될 거 같습니다.

  1. 여러 파일로 나누어 작성
  2. 외부 생성을 통한 의존성 주입

2번만 간단히 예시를 들어보면 아래와 같이 회피할 수 있습니다.

sample.ts

export const buildTitle = () => {
  return 'title';
};

export const buildDescription = () => {
  return 'description';
};

export const buildTitleAndDescription = (buildTitle: () => string, buildDescription: () => string): string => {
  const title = buildTitle();
  const description = buildDescription();

  return `${title},${description}`;
};

sample.test.ts

import * as sample from './sample';
import { vi } from 'vitest';

describe('Test', () => {
  test('Test buildTitleAndDescription()', () => {
    const mockedBuildTitle = vi.spyOn(sample, 'buildTitle').mockReturnValue('mocked-title');
    const mockedBuildDescription = vi.spyOn(sample, 'buildDescription').mockReturnValue('mocked-description');

    const result = sample.buildTitleAndDescription(sample.buildTitle, sample.buildDescription);
    expect(mockedBuildTitle).toHaveBeenCalled();
    expect(mockedBuildDescription).toHaveBeenCalled();
    expect(result).toBe('mocked-title,mocked-description');
  });
});

참고

여러 파일로 나누어 작성하는 것과 별 차이점은 없을 것 같습니다만, 같은 파일 내에서 작성하고 싶으시다면 아래와 같은 방법으로 작성해도 가능하긴 합니다.

sample.ts

export const sampleObject = {
  buildTitle: () => {
    return 'title';
  },
  buildDescription: () => {
    return 'description';
  },
};

export const buildTitleAndDescription = (): string => {
  const title = sampleObject.buildTitle();
  const description = sampleObject.buildDescription();

  return `${title},${description}`;
};

sample.test.ts

import { sampleObject, buildTitleAndDescription } from './sample';
import { vi } from 'vitest';

describe('Test', () => {
  test('Test buildTitleAndDescription()', () => {
    const mockedBuildTitle = vi.spyOn(sampleObject, 'buildTitle').mockReturnValue('mocked-title');
    const mockedBuildDescription = vi.spyOn(sampleObject, 'buildDescription').mockReturnValue('mocked-description');

    const result = buildTitleAndDescription();
    expect(mockedBuildTitle).toHaveBeenCalled();
    expect(mockedBuildDescription).toHaveBeenCalled();
    expect(result).toBe('mocked-title,mocked-description');
  });
});

참고자료