테스트

Vitest document is not defined 오류 해결: jsdom 설정 기준

2026.05.17·수정 2026.05.19·약 17분

이 글에서 정리하는 내용

Vitest document is not defined 오류는 React 컴포넌트 테스트에서 자주 만나는 환경 설정 문제입니다. 테스트 코드는 브라우저의 document를 사용하려고 하는데, Vitest 실행 환경이 기본값인 node로 잡혀 있으면 DOM API를 찾지 못합니다. 이 글에서는 jsdom 설정, 테스트 파일 단위 환경 분리, @testing-library/jest-dom 연결, TypeScript 타입 오류까지 한 번에 정리합니다.

Vitest document is not defined 오류 메시지에서 먼저 봐야 할 부분

Vitest document is not defined 오류 해결을 위한 jsdom 환경 진단 흐름

Vitest로 React 컴포넌트 테스트를 작성하다 보면 아래처럼 document 또는 window를 찾을 수 없다는 오류가 나올 수 있습니다.

ReferenceError: document is not defined
ReferenceError: window is not defined

처음 보면 render 함수나 expect 문법을 잘못 쓴 것처럼 보입니다. 하지만 실제 원인은 테스트 코드 자체보다 테스트 실행 환경에 있는 경우가 많습니다. 아래 테스트는 React Testing Library를 사용하는 일반적인 컴포넌트 테스트입니다.

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders title', () => {
  render(<App />);

  expect(screen.getByText('Hello')).toBeInTheDocument();
});

이 코드에서 핵심은 render(<App />)입니다. 컴포넌트를 렌더링하려면 테스트 안에 가상의 DOM이 있어야 합니다. 브라우저에서는 document, window, HTMLElement 같은 객체가 기본으로 존재하지만, Vitest의 기본 실행 환경인 Node에서는 이런 브라우저 객체가 자동으로 제공되지 않습니다.

따라서 이 오류를 만나면 먼저 질문을 바꿔야 합니다. “테스트 문법이 틀렸나?”가 아니라 “이 테스트가 브라우저 DOM을 필요로 하는데 Node 환경에서 실행되고 있나?”를 확인하는 쪽이 빠릅니다. 버튼 클릭, input 입력, 화면 텍스트 조회, 컴포넌트 렌더링이 들어간 테스트라면 대부분 DOM 환경이 필요합니다.

Vitest 기본 환경과 브라우저 DOM의 차이

Vitest의 테스트 환경 기본값은 node입니다. Node 환경은 유틸 함수, 데이터 변환 함수, 서버 사이드 로직처럼 브라우저 화면이 필요 없는 테스트에 적합합니다. 반대로 React 컴포넌트 테스트는 화면 구조를 만들어야 하므로 브라우저와 비슷한 환경이 필요합니다.

아래처럼 단순 계산 함수만 테스트한다면 document가 필요하지 않습니다. 이런 테스트는 Node 환경에서도 정상적으로 실행됩니다.

export function sum(a: number, b: number) {
  return a + b;
}
import { expect, test } from 'vitest';
import { sum } from './sum';

test('adds numbers', () => {
  expect(sum(1, 2)).toBe(3);
});

하지만 컴포넌트 테스트는 다릅니다. @testing-library/reactrender는 컴포넌트를 DOM에 올리고, screen.getByText는 그 DOM 안에서 텍스트를 찾습니다. 이 흐름에서는 document.body 같은 브라우저 API가 필요합니다.

환경주로 맞는 테스트주의할 점
node순수 함수, 유틸 함수, 서버 로직브라우저 DOM API가 없습니다.
jsdomReact 컴포넌트, Testing Library 기반 단위 테스트브라우저를 흉내 내지만 실제 렌더링 엔진은 아닙니다.
happy-dom빠른 DOM 시뮬레이션이 필요한 테스트일부 브라우저 API 호환성을 확인해야 합니다.
Browser Mode브라우저 기반 통합 테스트 또는 실제 브라우저 동작 검증설정과 실행 비용이 단위 테스트보다 큽니다.

document is not defined는 테스트 코드가 무조건 잘못됐다는 뜻이 아닙니다. 테스트가 요구하는 환경과 현재 Vitest 실행 환경이 맞지 않는다는 신호에 가깝습니다. 이 기준만 잡아도 수정 범위가 vitest.config.ts, vite.config.ts, 테스트 파일 상단 환경 주석, setup 파일 쪽으로 좁혀집니다.

프로젝트 전체에 jsdom 적용하기

React 컴포넌트 테스트가 프로젝트의 대부분을 차지한다면 전역 환경을 jsdom으로 잡는 방식이 가장 단순합니다. 먼저 필요한 패키지를 개발 의존성으로 설치합니다.

npm i -D vitest jsdom @testing-library/react @testing-library/jest-dom

이미 Vitest와 React Testing Library가 설치되어 있다면 jsdom@testing-library/jest-dom만 추가하면 됩니다. 그다음 vitest.config.tstest.environment를 설정합니다.

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

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts']
  }
});

이 설정에서 environment: 'jsdom'이 핵심입니다. setupFiles는 뒤에서 다룰 matcher 확장 파일을 불러오기 위해 함께 넣었습니다. document is not defined만 해결하려면 environment가 우선이고, toBeInTheDocument 같은 matcher 오류까지 줄이려면 setup 파일도 같이 연결하는 것이 좋습니다.

Vite 프로젝트에서는 vite.config.ts 안에 Vitest 설정을 함께 둘 수도 있습니다. 이때 defineConfigvite에서 가져오는 구조라면 테스트 설정 타입 인식을 위해 /// <reference types="vitest/config" />를 추가하는 방식도 자주 사용됩니다.

/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts']
  }
});

별도 vitest.config.ts를 만들면 테스트 설정을 분리하기 쉽습니다. 다만 이 경우 기존 vite.config.ts의 플러그인, alias, resolve 설정을 그대로 가져오지 못해 문제가 생길 수 있습니다. React 플러그인이나 경로 alias가 테스트에서도 필요하다면 별도 설정에 다시 넣거나 mergeConfig로 Vite 설정과 합치는 방식을 고려해야 합니다.

node 환경으로 남아 있는 설정 예시

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node'
  }
});

위 설정은 순수 함수 테스트에는 문제가 없지만 React 컴포넌트 렌더링 테스트에는 맞지 않습니다. 오류 메시지에 documentwindow가 보인다면 먼저 이 값이 jsdom으로 바뀌어 있는지 확인합니다.

테스트 파일 단위로 환경 나누기

모든 테스트를 jsdom으로 돌릴 필요는 없습니다. 프로젝트 안에 유틸 함수 테스트와 컴포넌트 테스트가 섞여 있다면 파일 단위로 환경을 나누는 편이 더 명확할 수 있습니다.

Vitest는 테스트 파일 상단의 주석으로 해당 파일에서 사용할 환경을 지정할 수 있습니다. 특정 테스트 파일만 DOM 환경이 필요하다면 아래처럼 작성합니다.

/**
 * @vitest-environment jsdom
 */
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders title', () => {
  render(<App />);

  expect(screen.getByText('Hello')).toBeInTheDocument();
});

이 방식은 테스트 수가 많지 않거나 일부 컴포넌트 테스트만 DOM을 필요로 할 때 유용합니다. 예를 들어 src/utils의 함수 테스트는 Node로 두고, src/components의 컴포넌트 테스트만 jsdom으로 실행할 수 있습니다.

다만 컴포넌트 테스트 파일이 계속 늘어나는 프로젝트라면 매번 주석을 붙이는 관리 비용이 생깁니다. 그런 경우에는 전역 환경을 jsdom으로 두고, DOM이 전혀 필요 없는 테스트만 별도 프로젝트나 별도 설정으로 분리하는 쪽이 더 단순할 수 있습니다.

toBeInTheDocument 오류까지 같이 해결하기

document is not defined를 해결한 뒤에도 테스트가 바로 통과하지 않을 수 있습니다. 그다음 자주 보이는 오류가 아래 형태입니다.

TypeError: expect(...).toBeInTheDocument is not a function
Property 'toBeInTheDocument' does not exist on type ...

이 오류는 jsdom 환경 설정 문제가 아니라 matcher 연결 문제입니다. toBeInTheDocument, toBeVisible, toHaveTextContent 같은 matcher는 Vitest 기본 matcher가 아닙니다. Testing Library의 jest-dom 확장을 setup 파일에서 한 번 불러와야 합니다.

import '@testing-library/jest-dom/vitest';

예를 들어 위 코드를 src/test/setup.ts에 작성했다면, 설정 파일에서 해당 파일을 setupFiles로 연결합니다.

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

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts']
  }
});

Jest 예제를 보고 import '@testing-library/jest-dom'만 복사해오는 경우가 있습니다. Vitest에서는 Vitest용 엔트리인 @testing-library/jest-dom/vitest를 사용하는 쪽이 설정 의도가 더 분명합니다. 특히 Jest에서 Vitest로 테스트 환경을 옮기는 중이라면 이 부분을 먼저 비교해 보는 것이 좋습니다.

TypeScript 타입 오류도 같이 정리하기

테스트 실행은 되는데 에디터나 타입 검사에서 matcher를 모른다고 표시된다면 tsconfig의 타입 포함 범위를 확인합니다. 프로젝트 설정에 따라 아래처럼 Vitest와 jest-dom 타입을 추가할 수 있습니다.

{
  "compilerOptions": {
    "types": ["vitest/globals", "@testing-library/jest-dom"]
  },
  "include": ["src", "src/test/setup.ts"]
}

globals 옵션을 켜지 않고 테스트 파일마다 test, expect를 직접 import하는 방식이라면 vitest/globals가 꼭 필요하지 않을 수 있습니다. 중요한 것은 런타임 설정과 타입 설정을 같은 방향으로 맞추는 것입니다. 테스트는 통과하는데 타입만 빨간 줄이 남는다면 setup 파일이 include 범위에 들어 있는지도 같이 봐야 합니다.

설정 후에도 실패할 때 점검할 순서

Vitest jsdom 설정 후 Testing Library matcher와 타입 오류를 점검하는 흐름

jsdom을 설정했는데도 같은 오류가 반복된다면 코드를 계속 바꾸기보다 실행 기준을 다시 확인해야 합니다. 실제 작업에서는 설정 내용보다 “내가 수정한 설정 파일을 Vitest가 읽고 있는지”에서 막히는 경우가 적지 않습니다.

  • jsdom 패키지가 프로젝트의 개발 의존성에 설치되어 있는지 봅니다.
  • vitest.config.tsvite.config.ts 중 실제 테스트 명령이 어떤 설정을 읽는지 확인합니다.
  • watch 모드가 켜져 있었다면 프로세스를 종료한 뒤 다시 실행합니다.
  • 모노레포라면 테스트를 실행하는 패키지 위치에 필요한 설정과 의존성이 있는지 봅니다.
  • setupFiles 경로가 현재 작업 디렉터리 기준으로 맞는지 확인합니다.
  • document 오류와 matcher 타입 오류를 같은 원인으로 묶어 보지 않습니다.

예를 들어 루트에는 vitest.config.ts가 있고 실제 테스트는 packages/ui에서 실행하는 구조라면 루트 설정이 적용되지 않을 수 있습니다. 이때는 패키지별 설정을 만들거나, 테스트 스크립트에서 사용할 설정 파일을 명시합니다.

{
  "scripts": {
    "test": "vitest --config vitest.config.ts",
    "test:watch": "vitest --config vitest.config.ts --watch"
  }
}

CSS import에서 다른 오류가 이어지는 경우도 있습니다. jsdom 환경에서는 브라우저와 비슷한 처리를 하지만, 외부 패키지가 CSS나 asset을 직접 import하는 구조에서는 의존성 inline 설정이 필요할 수 있습니다. 이 경우 오류 메시지가 document is not defined에서 unknown extension .css 같은 형태로 바뀌므로 같은 문제로 보지 않는 것이 좋습니다.

또한 jsdom은 실제 브라우저가 아닙니다. 화면 크기 계산, 스크롤 위치, 레이아웃 렌더링, 브라우저별 동작처럼 실제 렌더링 엔진이 필요한 테스트는 jsdom 안에서 억지로 해결하기 어렵습니다. 컴포넌트가 조건에 따라 렌더링되는지 확인하는 단위 테스트는 Vitest와 jsdom으로 처리하고, 실제 브라우저 동작은 Playwright 같은 도구로 분리하는 편이 관리하기 쉽습니다.

다음에 다시 볼 기준

Vitest document is not defined 오류는 대부분 테스트 환경 문제입니다. 컴포넌트 렌더링, 사용자 이벤트, 화면 텍스트 조회가 들어간 테스트라면 먼저 environment: 'jsdom' 설정을 확인합니다.

그다음에는 오류가 바뀌었는지 봐야 합니다. document 오류가 사라지고 toBeInTheDocument 관련 오류가 남았다면 DOM 환경은 어느 정도 해결된 것이고, 남은 문제는 @testing-library/jest-dom/vitest 연결 또는 TypeScript 타입 설정 쪽일 가능성이 큽니다.

정리하면 다음 순서로 보면 됩니다.

  • 테스트가 DOM을 필요로 하는지 확인합니다.
  • jsdom 또는 happy-dom 중 필요한 환경을 선택합니다.
  • test.environmentjsdom으로 설정되어 있는지 봅니다.
  • 일부 파일만 DOM이 필요하면 @vitest-environment jsdom 주석을 사용합니다.
  • toBeInTheDocument 오류가 남으면 setup 파일과 matcher import를 확인합니다.
  • 타입 오류가 남으면 tsconfigtypesinclude 범위를 확인합니다.
  • 실제 브라우저 렌더링 검증은 jsdom 단위 테스트와 분리합니다.

Vitest는 빠르게 테스트를 돌리기 좋은 도구지만, 기본 실행 환경이 브라우저가 아니라 Node라는 점을 놓치면 컴포넌트 테스트 초반에 같은 오류를 반복해서 만나게 됩니다. 이 오류는 테스트 코드를 많이 고치는 것보다, 테스트가 필요한 환경을 정확히 맞추는 쪽에서 해결하는 것이 더 빠릅니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기