VITE + VITEST (migrate from Jest)

saewoong jeon·2022년 9월 2일
10
post-thumbnail

Vite

Vite의 사전 번들링 기능은 Esbuild를 사용하고 있습니다. Go로 작성된 Esbuild는 Webpack, Parcel과 같은 기존의 번들러 대비 10-100배 빠른 번들링 속도를 보였죠.
(https://vitejs-kr.github.io/guide/why.html#the-problems)

최근 진행중인 프로젝트에는 Vite가 도입되었습니다.
몇가지 낯선 설정들만 제외하면 Webpack에 비하여
'확연히' 빠른 빌드 속도로 인해 개발 향상성에 큰 도움을 주고 있습니다.

기존에 Webpack을 이용하고 계시다면 꼭 한번 경험해보시기를 추천 합니다.

Vitest : Blazing Fast Unit Test Framework

본격적으로 Test관련 내용입니다.
vitest는 vite 환경으로 테스트를 진행할수 있도록 도와주는 프레임워크입니다.
기존에 vite환경에서 개발을 진행하고 있다면, 별도의 설정없이 test코드 작성이 가능합니다.
또한, vite가 webpack환경보다 빠른 개발 서버 로드 속도를 보여준 것과 같은 이유로 test 수행속도가 '확연히' 뛰어납니다.

설정

import.meta.env.VITE_XXX

vite 에서는 import.meta.env.VITE_XXX 와 같이 환경 변수를 사용하는데
WEBPACK 환경에서 위와 같은 환경 변수를 사용하기 위해서는 별도의 babel 관련 설정이 추가되어야 합니다.

jest로 test 환경을 구성하기 위해서는 상당히 많은 babel및 부가 설정들을 세팅해 주어야 원하는 결과를 얻을 수 있습니다.

vite를 사용하는 이유 중 하나가 dependencies를 최소화 할 수 있다는 점이 었는데, 테스트를 위해서 해당 설정들을 추가하는 것이 합리적이지 않다는 생각이 들었습니다.

package.json (with Jest)

 "devDependencies": {
    "@babel/core": "^7.17.12",
    "@babel/node": "^7.18.10",
    "@babel/plugin-transform-modules-commonjs": "^7.18.6",
    "@babel/preset-env": "^7.18.10",
    "@babel/preset-typescript": "^7.18.6",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.3.0",
    "@types/jest": "^28.1.6",
    "babel-jest": "^28.1.3",
    "babel-loader": "^8.2.5",
    "jest": "^28.1.3",
    "jest-environment-jsdom": "^28.1.3",
    "jest-jasmine2": "^28.1.3",
    "jest-plugin-context": "^2.9.0",
    "jest-styled-components": "^7.1.0",
    "jest-svg-transformer": "^1.0.0",
    "axios-mock-adapter": "^1.21.2",
    "ts-jest": "^28.0.7",
    "typescript": "^4.6.3",
  }

package.json (with Vitest)

"devDependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.3.0",
    "@vitejs/plugin-react": "^1.3.2",
    "@vitest/coverage-c8": "^0.22.1",
    "axios-mock-adapter": "^1.21.2",
    "jsdom": "^20.0.0",
    "typescript": "^4.6.3",
    "vitest": "^0.22.1",
  }

위는 Vitest 환경에서의 package.json 파일 중 일부입니다.
babel 관련 플러그인들이 더이상 필요하지 않습니다.

당연히 Jest환경을 위해 필요했던 babel.config.js도 더이상 필요하지 않습니다. vite에서는 필요하지도 않던 설정들이 test 환경만을 위해 추가되어 있었는데 이제 좀 정상으로 돌아간 느낌입니다.

jest.config.js

// jest.config.js
module.exports = {
  coverageProvider: "v8",
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.(ts|tsx)?$': 'ts-jest',
    '^.+\\.(js|jsx)?$': 'babel-jest',
  },
  moduleNameMapper: {
    // stub out CSS imports per Jest's recommendation
    "\\.(css)$": "identity-obj-proxy",
    ...
  },
};

jest.config.js 파일입니다. vite.config.ts에 설정해둔 설정과는 별도로 test에 필요한 설정을 추가해줘야 했습니다.
특히 rootdir, moduleNameMapper 등은 tsconfig에 들어 있는 사항들이 중복으로 추가 되어있습니다.

vite에서 추가된 부분

// vite.config.ts
export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles:  'test/setup.ts',
  },
})
// test/setup.ts 

import '@testing-library/jest-dom'
import '@testing-library/react'

// tsconfig.json
{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

Vitest를 위해 추가 및 변경된 부분입니다.
jest.setup.js 를 setup파일로 변경후 vite test환경에 추가해 주었습니다.

TEST 작성

기본적으로 jest와 마찬가지로 xxx.test.tsx 형식의 파일에 작성해 주면 테스트 파일로 인식합니다.
몇가지만 제외하면 변경 사항이 많지 않습니다.

mock

// jest
const mockedUsedNavigate = jest.fn((path: string) => {})
jest.mock('react-router-dom', () => ({
  useNavigate: () => mockedUsedNavigate,
}))

// vitest
import { describe, it, expect, beforeEach, vi } from 'vitest'
const mockedUsedNavigate = vi.fn((path: string) => {})
vi.mock('react-router-dom', () => ({
  useNavigate: () => mockedUsedNavigate,
}))

로그인 컴포넌트 테스트에서는 로그인 처리후 라우팅 여부를 확인하기 위해 react-router-dom 모듈을 mocking했습니다.

jest.mock 대신 vitest에서 제공하는 vi를 사용하여 함수를 mocking할 수 있습니다.

예제

import MockAdapter from 'axios-mock-adapter'

const mockServer = new MockAdapter(axiosInstance, { delayResponse: 200, onNoMatch: 'throwException' })
  .onPost('/api/login', { id: 'test', password: 'test' })
  .reply(200, {})


describe('<Login />', () => {
 it('<Login /> 로그인 성공시 홈화면 이동', async () => {
    //
    const { idInput, passwordInput, loginButton } = setup()

    //
    const loginData = { id: 'test', password: 'test' }
    fireEvent.change(idInput, { target: { value: loginData.accountId } })
    fireEvent.change(passwordInput, { target: { value: loginData.password } })
    fireEvent.click(loginButton)
    await waitFor(() => {
      expect(mockServer.history.post.length).toBe(1)
      expect(mockServer.history.post[0].url).toEqual('/api/login')
      expect(mockServer.history.post[0].data).toBe(JSON.stringify(loginData))
      expect(mockedUsedNavigate).toBeCalledTimes(1)
      expect(mockedUsedNavigate).toBeCalledWith('home')
    })
  })
})

api 호출에 따른 결과를 테스트 하기 위해 axios-mock-adapter를 사용한 간단한 예제입니다. jest에서의 코드와 동일합니다.

  • id,password를 입력 후 성공시 지정된 api 경로가 호출 되었는지
  • 호출의 횟수가 1번인지
  • 로그인 인증 성공시 지정된 경로로 이동하는지
  • 이동하는 nav함수가 1번만 호출되었는지를 테스트합니다.

테스트 작성에 대한 경험이 많이 없어 테스트 구조 및 진행 방법에 대해서는 부족한 부분이 많은것 같습니다. 우선은 테스트의 동작 확인 위한 코드로 생각해 주시면 감사하겠습니다.

속도

jest

$ jest  --verbose
 PASS  src/pages/Login/Login.test.tsx (26.176 s)
  <Login />
    ✓ Matches DOM Snapshot (54 ms)<Login /> 로그인 페이지 컴포넌트 랜더링 (15 ms)<Login /> 로그인 성공시 홈화면 이동 (270 ms)<Login /> 로그인 실패시 동작 없음 (69 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   1 passed, 1 total
Time:        29.242 s
Ran all test suites.

vitest

 ✓ src/pages/Login/Login.test.tsx (4) 387ms
   ✓ <Login /> (4) 387ms
     ✓ Matches DOM Snapshot
     ✓ <Login /> 로그인 페이지 컴포넌트 랜더링
     ✓ <Login /> 로그인 실패시 동작 없음
     ✓ <Login /> 로그인 성공시 홈화면 이동

 Snapshots  1 obsolete
            ↳ src/pages/Login/Login.test.tsx
              · <Login /> Matches DOM Snapshot 1

Test Files  1 passed (1)
     Tests  4 passed (4)
  Start at  18:37:21
  Duration  8.25s (transform 1.70s, setup 1.17s, collect 5.57s, tests 387ms)

테스트 구성이 많지 않음에도 불구하고 3.5배 정도의 시간차이를 보여줍니다.
테스트 코드 작성의 중요성 과 필요성은 모두 알지만, 상당히 번거로운 작업이고, 시간도 많이 소요 되는데,
적어도 작성의 결과물을 볼 수 있는 시간이 줄어들어 조금 더 빠른 퇴근을 도와주는 것 같습니다.

ETC

vitest 전용 vsCode 플러그인도 적용할 수 있는데, 제가 부족함이 있는건지 제가 구성한 환경에서는 작동하지 않아 버려두었습니다. 대신 @vitest/ui 설치 후 vitest --ui 를 통해 ui 환경에서 동작을 확인할 수 있습니다.

vite가 ESM방식으로 동작한다는 것을 더 직관적으로 확인 할 수 있으며 모듈간의 관계도 확인할 수 있습니다.

concurrent 를 명시하여 parallel 하게 테스트를 진행할 수 있다고도 합니다. 테스트 규모가 늘어나면 많은 테스트를 수행하게 될텐데 concurrent를 활용하여 맥북을 조금 더 혹사 시킬 수 있을 것 같습니다.

concurrent가 가능한 것은 아래와 같은 차이에서 기인합니다.
jest는 하나의 global 객체를 구성하기 위해 시간이 오래 걸립니다.
반면, vitest 는 esm 빌드 방식의 특징때문에 각 테스트 모듈별 작은 어플리케인션이 별도로 동작합니다. 때문에 각각의 테스트가 parallel하게 동작할 수 있고 수정에도 큰 시간이 소요 되지 않습니다.

메모리 이슈가 발생할 확률이 있으니 아래 설정을 참고하여 그 수준을 잘 조절 할 필요가 있습니다.
https://vitest.dev/guide/features.html#threads

Conclusion

기존에 vite+jest 조합의 테스트 환경을 사용하고 있다면 시도해보지 않을 이유가 없는 기술 세팅이라 생각합니다. 개발과 테스트가 하나의 환경으로 동작할 수 있다는 것만으로 그 이유는 충분한 것 같습니다.

다만 vite 자체가 아직 보급이 많이 안되어 있고, vitest는 더욱더 안되어 있기 때문에 문제가 발생했을 때 참고할 만한 문서들이 적은 건 감안해야 할 것 같습니다.

또한 아직 v1.0.0 도 되지 않아 contributor 될 수 있는 기회가 찾아올 수 있음도 인지한 후 사용해야 할 것 같습니다.


Reference

0개의 댓글