error log

[vue/test-utils] document 접근 에러 해결 방법(with. attachTo 등)

by GicoMomg 2025. 2. 28.

- 에러 발생 환경: vue3, vue/test-utils, jest
- 에러 상황: 직접 DOM 접근 시 요소가 없다는 에러 발생

  • Vue 컴포넌트를 테스트할 때 DOM에 직접 접근하는 경우가 있다. Vue는 보통 ref로 DOM에 접근할 수 있으나, document.querySelectordocument.getElementById 도 사용이 가능하다.
  • 이런 경우 Jest와 vue/test-utils 환경에서는 컴포넌트가 jsdom의 가상 DOM에만 렌더링되므로 실제 document.body에 요소가 없어서 에러가 발생한다.
  • 이번 시간에는 에러 발생 원인과 attachTo 옵션을 비롯한 추가 해결 방법을 간단히 살펴보고자 한다.

1. DOM 접근 에러의 근본 원인

(1) jsdom 환경과 Virtual DOM

  • 테스트 환경에서 주로 사용하는 jsdom은 실제 브라우저의 DOM을 모방한다.
  • 빠른 테스트 실행과 독립적인 환경 구성이 가능하나, 실제 브라우저처럼 모든 DOM API와 이벤트를 지원하지 않는다.
  • vue/test-utils의 기본 마운트 방식은 컴포넌트를 jsdom의 가상 DOM에만 렌더링하므로 실제 document.body에 포함되지 않는다.

(2) 직접 DOM 접근의 한계

  • 컴포넌트 내부에서 ref를 사용하면 Vue가 제공하는 반응형 시스템과 컴포넌트 라이프사이클에 따라 안전하게 DOM에 접근할 수 있다.
  • 하지만 document.getElementByIddocument.querySelector로 접근하면 jsdom의 한계로 인해 해당 요소가 없어서 에러가 발생한다.
  • 예를 들어, 아래 코드는 컴포넌트가 jsdom의 가상 DOM에만 존재하여 요소를 찾지 못하고 실패한다.
import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

test('getElementById로 요소에 접근하기', () => {
  // MyComponent 내부에서 document.getElementById()로 요소를 찾으려고 할 때
  const element = document.getElementById('my-element')
  expect(element).not.toBeNull() // jsdom에 실제 요소가 없으므로 에러 발생
})



2. 해결 방법: attachTo 옵션 활용하기

  • vue/test-utils는 mount 함수의 옵션으로 attachTo를 제공한다. (공식문서)
  • 이 옵션을 사용하면 컴포넌트를 실제 DOM, 보통 document.body에 마운트할 수 있다.
  • 결과적으로 테스트 시 실제 DOM에 요소가 생성되어 document.getElementByIddocument.querySelector 호출이 정상적으로 동작한다.
import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

test('getElementById로 요소에 접근하기', () => {
  // 컴포넌트를 실제 document.body에 마운트하여 DOM 요소를 생성한다.
  const wrapper = mount(MyComponent, {
    attachTo: document.body
  })

  // 컴포넌트 내부의 특정 요소를 document.getElementById로 찾아 테스트 진행
  const element = document.getElementById('my-element')
  expect(element).not.toBeNull()
  expect(element.textContent).toBe('예상되는 텍스트')

  // 테스트 후 wrapper.unmount()를 호출해 메모리 누수를 방지한다.
  wrapper.unmount()
})
  • attachTo 옵션을 활용하면 컴포넌트가 실제 document.body에 마운트되어 DOM 접근 시 에러를 피할 수 있다.
  • 단, 테스트 종료 후 반드시 wrapper.unmount()로 컴포넌트를 해제해야 한다. 그렇지 않으면 여러 테스트 실행 시 메모리 누수가 발생할 수 있다.



3. 추가 해결 방법

추가로, 상황에 따라 다음 방법들을 고려할 수 있다!

(1) 테스트 전용 DOM 컨테이너 생성

  • 테스트 시작 시 document.body에 임의의 컨테이너 요소를 생성하고, 그 컨테이너에 컴포넌트를 마운트하는 방법이다. attachTo 옵션과 유사하지만, 테스트 환경을 보다 세밀하게 제어할 수 있다.
  • 한 예로, 모달 컴포넌트는 보통 특정 DOM 계층(예: #modal-root)에 마운트되어 동작하도록 설계된다.
  • 이 경우, 전용 컨테이너를 생성하면 실제 운영 환경과 유사하게 테스트할 수 있다.
import { mount } from '@vue/test-utils'
import ModalComponent from '@/components/ModalComponent.vue'

test('모달 컴포넌트가 커스텀 컨테이너에 정상적으로 마운트되어 동작하는지 확인한다.', () => {
  // 테스트 전용 컨테이너 생성
  const container = document.createElement('div')
  container.id = 'modal-root'
  document.body.appendChild(container)

  // ModalComponent를 커스텀 컨테이너에 마운트한다.
  const wrapper = mount(ModalComponent, {
    attachTo: container
  })

  // 예시: 모달이 활성화되면 'modal-active' 클래스가 추가되어야 한다.
  expect(wrapper.find('.modal-active').exists()).toBe(true)
  // 모달 내부에 특정 텍스트가 렌더링되는지 확인
  expect(wrapper.text()).toContain('모달 내용')

  // 테스트 종료 후 정리
  wrapper.unmount()
  document.body.removeChild(container)
})

(2) DOM API 모킹(Mock)하기

  • 직접 DOM 접근 대신 테스트 코드에서 DOM 관련 메서드를 모킹해 원하는 값을 반환하도록 설정할 수 있다.
  • 이 방법은 실제 DOM이 필요하지 않은 경우 유용하나, 실제 동작과 차이가 있을 수 있으므로 주의해야 한다.
test('getElementById 모킹 테스트', () => {
  const dummyElement = document.createElement('div')
  dummyElement.textContent = '예상되는 텍스트'
  // getElementById를 모킹해 항상 dummyElement를 반환하도록 설정한다.
  jest.spyOn(document, 'getElementById').mockReturnValue(dummyElement)

  const element = document.getElementById('my-element')
  expect(element).toBe(dummyElement)

  // 테스트 후 모킹한 메서드를 복원한다.
  document.getElementById.mockRestore()
})

(3) 테스트 전략 재검토

  • 가능하면 직접 DOM 접근 대신 Vue의 ref나 reactive 방식을 활용하는 것이 좋다.
  • 이 방법은 테스트 환경의 한계로 인한 문제를 근본적으로 회피할 수 있으며, Vue의 컴포넌트 라이프사이클에 맞춘 안정적인 DOM 접근을 보장한다.



4. 정리 및 결론

테스트 코드 작성 시 Vue 컴포넌트 내에서 직접 DOM API를 호출하는 경우 jsdom의 가상 DOM과 실제 DOM 간 차이로 인해 에러가 발생한다. attachTo 옵션을 사용하면 컴포넌트를 실제 document.body나 커스텀 컨테이너에 마운트해 문제를 효과적으로 해결할 수 있다.

또한, DOM API 모킹이나 테스트 전략 재검토를 통해 상황에 맞는 대안을 마련할 수 있다. 테스트 종료 후 반드시 wrapper.unmount()나 컨테이너 정리를 통해 메모리 누수를 방지해야 한다. 이번 포스트가 Vue 테스트 환경에서 DOM 접근 문제 해결에 도움이 되길 바란다.

반응형

댓글