Build/Testing

Vitest 맛보기

iam102 2023. 12. 13. 23:06

프로젝트를 진행 중에 특정 커스텀 훅에서 제대로 동작하지 못하는 버그로 인해 해당 이슈가 QA로 들어와 고치면서 단위 테스트에 대한 필요성을 많이 느끼게 되었습니다. 기존에 공용으로 사용 중인 account 모듈에 관해서만 Jest를 이용한 단위 테스트가 적용되어 있어서 현재 진행 중인 프로젝트가 Vite로 구성된 점을 토대로 Vitest를 한번 사용해보았고 간단히 구성을 소개할까 합니다.

 

Vitest?

Vitest는 Vite에서 사용되는 테스팅 프레임워크입니다. Jest에서 제공하는 API를 대체할 수 있도록 Vitest에서 호환 가능한 API를 제공하며 단위 테스트 설정에 필요한 mocking, snapshots, coverage의 기능도 포함되어 있습니다. Vitest는 다른 테스팅 프레임워크보다 가볍고 속도가 빠른 점을 내세우고 있지만 아직까지는 Vite 프로젝트의 테스트 러너로써 Vite 환경이 아니면 사용을 못합니다.

 

Vitest 기본 설정

※ react-ts vite 환경에서 셋업을 했습니다.

이전 Vite 포스팅을 통해 React와 Typescript 베이스의 Vite 프로젝트를 하나 만듭니다.

yarn create vite vitest-app --template react-ts

 

Vitest를 설치합니다.

npm install -D vitest

# or

yarn add -D vitest

 

React Component와 hooks test를 위해 추가 라이브러리를 설치합니다.

npm install -D jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom

# or

yarn add -D jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom

 

package.json에 커맨드를 추가해줍니다.

{
  "scripts": {
    ...
    "test": "vitest"
  }
}

 

Vitest config는 따로 파일을 만들지 않고 vite.config.ts에 설정이 가능합니다.(다르게 설정이 필요한 경우 vitest.config.ts를 만들어 설정 가능)

// vite.config.ts
/// <reference types="vitest" />
/// <reference types="vite/client" />

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
 }
})
// src/test/setup.ts
// toBeInTheDocument API를 사용하기 위함.
import '@testing-library/jest-dom';

필요에 따라 testing config를 설정해줍니다.(https://vitest.dev/config/) Jest와 같이 전역적으로 API를 사용하기 위해 global 옵션을 true로 설정했고 Web application에서 진행하기 때문에 테스트 환경을 jsdom(or happy-dom)으로 설정합니다. setupFiles 옵션을 통해 추가로 설정 파일을 맵핑할 수 있습니다.

 

Vitest test 진행

1. module testing

테스트를 위해 간단하게 sum 함수를 생성하겠습니다.

// sum.ts
export const sum = (a: number, b: number): number => {
  return a + b;
};

해당 모듈을 테스트하기 위해 테스트 코드를 작성합니다.

// sum.test.ts
import { describe, expect, it } from 'vitest';

import { sum } from './sum';

describe('sum test', () => {
  it('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
});

 

2. React hooks testing

테스트를 위해 간단하게 custom hook을 생성하겠습니다.

// useCounter.tsx
import { useCallback, useState } from 'react';

export const useCounter = () => {
  const [count, setCount] = useState<number>(0);

  const increment = useCallback(() => setCount((x) => x + 1), []);

  const decrement = useCallback(() => setCount((x) => x - 1), []);

  return { count, increment, decrement };
};

해당 custom hook을 테스트하기 위해 테스트 코드를 작성합니다.

// useCounter.test.tsx
import { describe, expect, it } from 'vitest';
import { act, renderHook } from '@testing-library/react';

import { useCounter } from './useCounter';

describe('useCounter test', () => {
  it('increment count', () => {
    const { result } = renderHook(() => useCounter());
    act(() => {
      result.current.increment();
    });
    expect(result.current.count).toBe(1);
  });

  it('decrement count', () => {
    const { result } = renderHook(() => useCounter());
    act(() => {
      result.current.decrement();
    });
    expect(result.current.count).toBe(-1);
  });
});

 

3. React components testing

Vite 프로젝트 생성 시 만들어진 App.tsx를 사용하겠습니다.

// App.tsx
import { useCounter } from './useCounter';
import reactLogo from './assets/react.svg';
import './App.css';

function App() {
  const { count, increment, decrement } = useCounter();

  return (
    <div className="App">
      <div>
        <a href="https://vitejs.dev" target="_blank" rel="noreferrer">
          <img src="/vite.svg" className="logo" alt="Vite logo" />
        </a>
        <a href="https://reactjs.org" target="_blank" rel="noreferrer">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <p>count is: {count}</p>
      <div className="card">
        <button onClick={() => increment()}>+</button>
        <button onClick={() => decrement()}>-</button>
      </div>
    </div>
  );
}

export default App;

해당 App component를 테스트하기 위해 테스트 코드를 작성합니다.

// App.test.tsx
import { describe, expect, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import App from './App';

describe('App test', () => {
  it('the title is visible', () => {
    render(<App />);
    expect(screen.getByText(/Vite \+ React/i)).toBeInTheDocument();
  });

  it('increase when + button is clicked', async () => {
    render(<App />);
    userEvent.click(screen.getByText('+'));
    expect(await screen.findByText(/count is: 1/i)).toBeInTheDocument();
  });

  it('decrease when - button is clicked', async () => {
    render(<App />);
    userEvent.click(screen.getByText('-'));
    expect(await screen.findByText(/count is: -1/i)).toBeInTheDocument();
  });
});

 

전반적으로 테스트 코드 작성이 Jest와 차이가 없어 거의 동일하게 사용이 가능했습니다. 다음은 Vitest로 동작한 테스트 코드의 결과를 보여주는 콘솔 창입니다.

 

마무리

아직 검증이 더 필요한 단계이지만 Vite로 구성된 프로젝트의 경우 편하게 테스팅 환경을 구축할 수 있었습니다. Vitest를 사용하는 것이 기존의 다른 테스팅 프레임워크 & 라이브러리를 사용하는 것과 큰 차이가 없어 러닝 커브가 크지 않았습니다.

State of JS 2022 Testing (https://2022.stateofjs.com/en-US/libraries/testing/)

 

아직 레퍼런스가 많이 없긴 하지만 State of JS 2022의 Testing 항목을 보면 Vitest가 많은 개발자들로부터 관심을 받고 있음을 알 수 있습니다. Vite로 프로젝트를 구성하는 경우 단위 테스트를 위해 Vitest의 도입을 한 번쯤 고민해보는 것도 좋겠습니다.


참고 자료

https://vitest.dev/guide/why.html

https://vitest.dev/config/

https://eternaldev.com/blog/testing-a-react-application-with-vitest/

https://dev.to/mbarzeev/from-jest-to-vitest-migration-and-benchmark-23pl