테스트

Nuxt 애플리케이션을 테스트하는 방법.
모듈 작성자인 경우, 모듈 작성자 가이드에서 더 구체적인 정보를 확인할 수 있습니다.

Nuxt는 @nuxt/test-utils를 통해 Nuxt 애플리케이션의 엔드 투 엔드 및 단위 테스트를 위한 일류 지원을 제공합니다. 이 라이브러리는 테스트 유틸리티와 설정을 모아둔 것으로, 현재 Nuxt 자체에서 사용하는 테스트와 모듈 생태계 전반의 테스트를 지원합니다.

설치

다른 테스트 의존성을 직접 관리할 수 있도록, @nuxt/test-utils는 다양한 선택적 피어 의존성과 함께 제공됩니다. 예를 들어:

  • 런타임 Nuxt 환경으로 happy-domjsdom 중에서 선택할 수 있습니다.
  • 엔드 투 엔드 테스트 러너로 vitest, cucumber, jest, playwright 중에서 선택할 수 있습니다.
  • 내장 브라우저 테스트 유틸리티를 사용하려면(그리고 테스트 러너로 @playwright/test를 사용하지 않는 경우) playwright-core만 필요합니다.
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core

단위 테스트

현재 Nuxt 런타임 환경이 필요한 코드를 위한 단위 테스트 환경을 제공합니다. 현재는 vitest만 지원 하며, 다른 런타임 추가에 대한 기여도 환영합니다.

설정

  1. @nuxt/test-utils/modulenuxt.config 파일에 추가합니다(선택 사항). 이 모듈은 Nuxt DevTools에 Vitest 통합을 추가하여 개발 중에 단위 테스트를 실행할 수 있게 해줍니다.
    export default defineNuxtConfig({
      modules: [
        '@nuxt/test-utils/module'
      ]
    })
    
  2. 다음과 같은 내용으로 vitest.config.ts 파일을 생성합니다:
    import { defineVitestConfig } from '@nuxt/test-utils/config'
    
    export default defineVitestConfig({
      // 필요한 Vitest 커스텀 설정
    })
    
vitest 설정에서 @nuxt/test-utils를 import할 때, package.json"type": "module"이 지정되어 있거나 vitest 설정 파일의 이름을 적절히 변경해야 합니다.

예: vitest.config.m{ts,js}.

.env.test 파일을 사용하여 테스트용 환경 변수를 설정할 수 있습니다.

Nuxt 런타임 환경 사용하기

기본적으로, @nuxt/test-utils는 기본 Vitest 환경을 변경하지 않으므로, 세밀하게 선택적으로 Nuxt 테스트를 다른 단위 테스트와 함께 실행할 수 있습니다.

테스트 파일 이름에 .nuxt.를 추가(예: my-file.nuxt.test.ts 또는 my-file.nuxt.spec.ts)하거나, 테스트 파일에 @vitest-environment nuxt 주석을 직접 추가하여 Nuxt 환경을 사용할 수 있습니다.

// @vitest-environment nuxt
import { test } from 'vitest'

test('my test', () => {
  // ... Nuxt 환경에서 테스트!
})

또는 Vitest 설정에서 environment: 'nuxt'를 설정하여 모든 테스트에 Nuxt 환경을 활성화할 수 있습니다.

// vitest.config.ts
import { fileURLToPath } from 'node:url'
import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    // 필요에 따라 Nuxt 전용 환경 옵션을 설정할 수 있습니다
    // environmentOptions: {
    //   nuxt: {
    //     rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
    //     domEnvironment: 'happy-dom', // 'happy-dom'(기본값) 또는 'jsdom'
    //     overrides: {
    //       // 전달하고 싶은 다른 Nuxt 설정
    //     }
    //   }
    // }
  }
})

기본적으로 environment: 'nuxt'를 설정한 경우, 필요에 따라 각 테스트 파일에서 기본 환경비활성화(opt out) 할 수 있습니다.

// @vitest-environment node
import { test } from 'vitest'

test('my test', () => {
  // ... Nuxt 환경 없이 테스트!
})
Nuxt 환경에서 테스트를 실행하면, happy-dom 또는 jsdom 환경에서 실행됩니다. 테스트가 실행되기 전에 글로벌 Nuxt 앱이 초기화됩니다(예: app.vue에 정의한 플러그인이나 코드 실행 포함).즉, 테스트에서 글로벌 상태를 변경하지 않도록 주의해야 하며(또는 필요하다면 이후에 반드시 초기화해야 합니다).

🎭 내장 Mock

@nuxt/test-utils는 DOM 환경을 위한 몇 가지 내장 mock을 제공합니다.

intersectionObserver

기본값은 true이며, IntersectionObserver API를 위한 기능 없는 더미 클래스를 생성합니다.

indexedDB

기본값은 false이며, fake-indexeddb를 사용하여 IndexedDB API의 동작하는 mock을 생성합니다.

이들은 vitest.config.ts 파일의 environmentOptions 섹션에서 설정할 수 있습니다:

import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environmentOptions: {
      nuxt: {
        mock: {
          intersectionObserver: true,
          indexedDb: true,
        }
      }
    }
  }
})

🛠️ 헬퍼

@nuxt/test-utils는 Nuxt 앱 테스트를 더 쉽게 해주는 다양한 헬퍼를 제공합니다.

mountSuspended

mountSuspended는 Nuxt 환경 내에서 어떤 Vue 컴포넌트든 마운트할 수 있게 해주며, 비동기 setup과 Nuxt 플러그인에서의 주입(injection)에 접근할 수 있습니다.

내부적으로 mountSuspended@vue/test-utilsmount를 감싸므로, 전달할 수 있는 옵션이나 사용법에 대해서는 Vue Test Utils 문서를 참고하세요.

예시:

// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'

it('can mount some component', async () => {
    const component = await mountSuspended(SomeComponent)
    expect(component.text()).toMatchInlineSnapshot(
        '"This is an auto-imported component"'
    )
})
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'

// tests/App.nuxt.spec.ts
it('can also mount an app', async () => {
    const component = await mountSuspended(App, { route: '/test' })
    expect(component.html()).toMatchInlineSnapshot(`
      "<div>This is an auto-imported component</div>
      <div> I am a global component </div>
      <div>/</div>
      <a href="/test"> Test link </a>"
    `)
})

renderSuspended

renderSuspended는 Nuxt 환경 내에서 @testing-library/vue를 사용하여 어떤 Vue 컴포넌트든 렌더링할 수 있게 해주며, 비동기 setup과 Nuxt 플러그인에서의 주입(injection)에 접근할 수 있습니다.

이 기능은 Testing Library의 유틸리티(예: screen, fireEvent)와 함께 사용해야 합니다. 사용하려면 프로젝트에 @testing-library/vue를 설치하세요.

또한, Testing Library는 정리를 위해 테스트 글로벌에 의존합니다. Vitest 설정에서 이를 활성화해야 합니다.

전달된 컴포넌트는 <div id="test-wrapper"></div> 내부에 렌더링됩니다.

예시:

// tests/components/SomeComponents.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
import { screen } from '@testing-library/vue'

it('can render some component', async () => {
  await renderSuspended(SomeComponent)
  expect(screen.getByText('This is an auto-imported component')).toBeDefined()
})
// tests/App.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'

it('can also render an app', async () => {
  const html = await renderSuspended(App, { route: '/test' })
  expect(html).toMatchInlineSnapshot(`
    "<div id="test-wrapper">
      <div>This is an auto-imported component</div>
      <div> I am a global component </div>
      <div>Index page</div><a href="/test"> Test link </a>
    </div>"
  `)
})

mockNuxtImport

mockNuxtImport를 사용하면 Nuxt의 자동 import 기능을 mock할 수 있습니다. 예를 들어, useStorage를 mock하려면 다음과 같이 할 수 있습니다:

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport('useStorage', () => {
  return () => {
    return { value: 'mocked storage' }
  }
})

// 여기에 테스트 작성
mockNuxtImport는 테스트 파일당 mock된 import마다 한 번만 사용할 수 있습니다. 실제로는 vi.mock으로 변환되는 매크로이며, Vitest 문서에서 설명한 대로 vi.mock은 호이스팅됩니다.

테스트마다 Nuxt import를 mock하고 다른 구현을 제공해야 한다면, vi.hoisted를 사용해 mock을 생성 및 노출한 뒤, 이를 mockNuxtImport에서 사용할 수 있습니다. 이렇게 하면 mock된 import에 접근할 수 있고, 테스트마다 구현을 변경할 수 있습니다. 테스트 간 mock 상태 변경을 되돌리려면 mock 복원을 각 테스트 전후에 반드시 수행하세요.

import { vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'

const { useStorageMock } = vi.hoisted(() => {
  return {
    useStorageMock: vi.fn(() => {
      return { value: 'mocked storage'}
    })
  }
})

mockNuxtImport('useStorage', () => {
  return useStorageMock
})

// 그런 다음, 테스트 내부에서
useStorageMock.mockImplementation(() => {
  return { value: 'something else' }
})

mockComponent

mockComponent를 사용하면 Nuxt의 컴포넌트를 mock할 수 있습니다. 첫 번째 인자는 PascalCase의 컴포넌트 이름이거나, 컴포넌트의 상대 경로입니다. 두 번째 인자는 mock 컴포넌트를 반환하는 팩토리 함수입니다.

예를 들어, MyComponent를 mock하려면 다음과 같이 할 수 있습니다:

import { mockComponent } from '@nuxt/test-utils/runtime'

mockComponent('MyComponent', {
  props: {
    value: String
  },
  setup(props) {
    // ...
  }
})

// 상대 경로나 별칭도 사용 가능
mockComponent('~/components/my-component.vue', async () => {
  // 또는 팩토리 함수
  return defineComponent({
    setup(props) {
      // ...
    }
  })
})

// 또는 SFC를 사용해 mock 컴포넌트로 리디렉션할 수 있습니다
mockComponent('MyComponent', () => import('./MockComponent.vue'))

// 여기에 테스트 작성

참고: 팩토리 함수 내에서 지역 변수를 참조할 수 없습니다. 팩토리 함수는 호이스팅되기 때문입니다. Vue API나 다른 변수를 사용해야 한다면, 팩토리 함수 내에서 import해야 합니다.

import { mockComponent } from '@nuxt/test-utils/runtime'

mockComponent('MyComponent', async () => {
  const { ref, h } = await import('vue')

  return defineComponent({
    setup(props) {
      const counter = ref(0)
      return () => h('div', null, counter.value)
    }
  })
})

registerEndpoint

registerEndpoint를 사용하면 mock 데이터를 반환하는 Nitro 엔드포인트를 만들 수 있습니다. API에 요청을 보내 데이터를 표시하는 컴포넌트를 테스트할 때 유용합니다.

첫 번째 인자는 엔드포인트 이름(예: /test/)입니다. 두 번째 인자는 mock 데이터를 반환하는 팩토리 함수입니다.

예를 들어, /test/ 엔드포인트를 mock하려면 다음과 같이 할 수 있습니다:

import { registerEndpoint } from '@nuxt/test-utils/runtime'

registerEndpoint('/test/', () => ({
  test: 'test-field'
}))

기본적으로 요청은 GET 메서드를 사용합니다. 함수 대신 객체를 두 번째 인자로 전달하여 다른 메서드를 사용할 수도 있습니다.

import { registerEndpoint } from '@nuxt/test-utils/runtime'

registerEndpoint('/test/', {
  method: 'POST',
  handler: () => ({ test: 'test-field' })
})

참고: 컴포넌트 내 요청이 외부 API로 가는 경우, baseURL을 사용하고 Nuxt 환경 오버라이드 설정 ($test)을 통해 비워두면 모든 요청이 Nitro 서버로 가게 할 수 있습니다.

엔드 투 엔드 테스트와의 충돌

@nuxt/test-utils/runtime@nuxt/test-utils/e2e는 서로 다른 테스트 환경에서 실행되어야 하므로 같은 파일에서 사용할 수 없습니다.

@nuxt/test-utils의 엔드 투 엔드 및 단위 테스트 기능을 모두 사용하려면, 테스트를 별도의 파일로 분리하세요. 그런 다음, 파일별로 특별한 // @vitest-environment nuxt 주석을 사용하거나, 런타임 단위 테스트 파일 이름에 .nuxt.spec.ts 확장자를 사용하면 됩니다.

app.nuxt.spec.ts

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport('useStorage', () => {
  return () => {
    return { value: 'mocked storage' }
  }
})

app.e2e.spec.ts

import { setup, $fetch } from '@nuxt/test-utils/e2e'

await setup({
  setupTimeout: 10000,
})

// ...

@vue/test-utils 사용하기

Nuxt에서 단위 테스트를 위해 @vue/test-utils만 단독으로 사용하고 싶고, Nuxt 컴포저블, 자동 import, context에 의존하지 않는 컴포넌트만 테스트한다면 다음 단계를 따라 설정할 수 있습니다.

  1. 필요한 의존성 설치
    npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
    
  2. 다음과 같은 내용으로 vitest.config.ts 파일을 생성합니다:
    import { defineConfig } from 'vitest/config'
    import vue from '@vitejs/plugin-vue'
    
    export default defineConfig({
      plugins: [vue()],
      test: {
        environment: 'happy-dom',
      },
    });
    
  3. package.json에 테스트 명령어 추가
    "scripts": {
      "build": "nuxt build",
      "dev": "nuxt dev",
      ...
      "test": "vitest"
    },
    
  4. 다음과 같은 내용으로 간단한 <HelloWorld> 컴포넌트 components/HelloWorld.vue 생성
    <template>
      <p>Hello world</p>
    </template>
    
  5. 새로 만든 컴포넌트에 대한 간단한 단위 테스트 ~/components/HelloWorld.spec.ts 생성
    import { describe, it, expect } from 'vitest'
    import { mount } from '@vue/test-utils'
    
    import HelloWorld from './HelloWorld.vue'
    
    describe('HelloWorld', () => {
      it('component renders Hello world properly', () => {
        const wrapper = mount(HelloWorld)
        expect(wrapper.text()).toContain('Hello world')
      })
    })
    
  6. vitest 명령어 실행
    npm run test
    

축하합니다! 이제 Nuxt에서 @vue/test-utils로 단위 테스트를 시작할 준비가 되었습니다. 즐거운 테스트 되세요!

엔드 투 엔드 테스트

엔드 투 엔드 테스트를 위해 Vitest, Jest, Cucumber, Playwright를 테스트 러너로 지원합니다.

설정

@nuxt/test-utils/e2e 헬퍼 메서드를 사용하는 각 describe 블록에서, 시작 전에 테스트 컨텍스트를 설정해야 합니다.

test/my-test.spec.ts
import { describe, test } from 'vitest'
import { setup, $fetch } from '@nuxt/test-utils/e2e'

describe('My test', async () => {
  await setup({
    // 테스트 컨텍스트 옵션
  })

  test('my test', () => {
    // ...
  })
})

내부적으로, setupbeforeAll, beforeEach, afterEach, afterAll에서 여러 작업을 수행하여 Nuxt 테스트 환경을 올바르게 설정합니다.

아래 옵션을 setup 메서드에 사용할 수 있습니다.

Nuxt 설정

  • rootDir: 테스트할 Nuxt 앱이 있는 디렉터리 경로.
    • 타입: string
    • 기본값: '.'
  • configFile: 설정 파일 이름.
    • 타입: string
    • 기본값: 'nuxt.config'

타이밍

  • setupTimeout: setupTest가 작업(빌드 또는 Nuxt 애플리케이션을 위한 파일 생성 등)을 완료하는 데 허용되는 시간(밀리초).
    • 타입: number
    • 기본값: 60000

기능

  • build: 별도의 빌드 단계를 실행할지 여부.
    • 타입: boolean
    • 기본값: true (browser 또는 server가 비활성화되었거나, host가 제공된 경우 false)
  • server: 테스트 스위트에서 요청에 응답할 서버를 실행할지 여부.
    • 타입: boolean
    • 기본값: true (host가 제공된 경우 false)
  • port: 제공된 경우, 실행되는 테스트 서버의 포트를 해당 값으로 설정.
    • 타입: number | undefined
    • 기본값: undefined
  • host: 제공된 경우, 새 서버를 빌드 및 실행하는 대신 테스트 대상으로 사용할 URL. 배포된 애플리케이션이나 이미 실행 중인 로컬 서버에 대해 "실제" 엔드 투 엔드 테스트를 실행할 때 유용합니다(테스트 실행 시간을 크게 단축할 수 있음). 아래 대상 호스트 엔드 투 엔드 예제 참고.
    • 타입: string
    • 기본값: undefined
  • browser: 내부적으로 Nuxt 테스트 유틸은 playwright를 사용해 브라우저 테스트를 수행합니다. 이 옵션이 설정되면 브라우저가 실행되며, 이후 테스트 스위트에서 제어할 수 있습니다.
    • 타입: boolean
    • 기본값: false
  • browserOptions
    • 타입: 다음 속성을 가진 object
      • type: 실행할 브라우저 타입 - chromium, firefox, webkit 중 하나
      • launch: 브라우저 실행 시 playwright에 전달할 옵션 객체. 전체 API 참조 참고.
  • runner: 테스트 스위트의 러너 지정. 현재는 Vitest가 권장됩니다.
    • 타입: 'vitest' | 'jest' | 'cucumber'
    • 기본값: 'vitest'
대상 host 엔드 투 엔드 예제

엔드 투 엔드 테스트의 일반적인 사용 사례는, 프로덕션에서 사용하는 것과 동일한 환경에서 실행 중인 배포 애플리케이션에 대해 테스트를 실행하는 것입니다.

로컬 개발 또는 자동 배포 파이프라인에서는 별도의 로컬 서버에 대해 테스트하는 것이 더 효율적이며, 테스트 프레임워크가 테스트마다 다시 빌드하는 것보다 일반적으로 더 빠릅니다.

엔드 투 엔드 테스트를 위해 별도의 대상 호스트를 사용하려면, setup 함수의 host 속성에 원하는 URL을 전달하면 됩니다.

import { setup, createPage } from '@nuxt/test-utils/e2e'
import { describe, it, expect } from 'vitest'

describe('login page', async () => {
  await setup({
    host: 'http://localhost:8787',
  })

  it('displays the email and password fields', async () => {
    const page = await createPage('/login')
    expect(await page.getByTestId('email').isVisible()).toBe(true)
    expect(await page.getByTestId('password').isVisible()).toBe(true)
  })
})

API

$fetch(url)

서버 렌더링된 페이지의 HTML을 가져옵니다.

import { $fetch } from '@nuxt/test-utils/e2e'

const html = await $fetch('/')

fetch(url)

서버 렌더링된 페이지의 응답을 가져옵니다.

import { fetch } from '@nuxt/test-utils/e2e'

const res = await fetch('/')
const { body, headers } = res

url(path)

주어진 페이지의 전체 URL(테스트 서버가 실행 중인 포트 포함)을 가져옵니다.

import { url } from '@nuxt/test-utils/e2e'

const pageUrl = url('/page')
// 'http://localhost:6840/page'

브라우저에서 테스트하기

@nuxt/test-utils 내에서 Playwright를 사용한 내장 지원을 제공합니다. 프로그래밍 방식 또는 Playwright 테스트 러너를 통해 사용할 수 있습니다.

createPage(url)

vitest, jest, cucumber 내에서, createPage로 설정된 Playwright 브라우저 인스턴스를 만들고(선택적으로) 실행 중인 서버의 경로로 이동할 수 있습니다. 사용 가능한 API 메서드에 대해서는 Playwright 문서를 참고하세요.

import { createPage } from '@nuxt/test-utils/e2e'

const page = await createPage('/page')
// `page` 변수에서 모든 Playwright API에 접근할 수 있습니다

Playwright 테스트 러너로 테스트하기

Playwright 테스트 러너 내에서 Nuxt 테스트를 위한 일류 지원도 제공합니다.

npm i --save-dev @playwright/test @nuxt/test-utils

이전 섹션에서 언급한 setup() 함수와 동일한 설정 세부 정보로 글로벌 Nuxt 설정을 제공할 수 있습니다.

playwright.config.ts
import { fileURLToPath } from 'node:url'
import { defineConfig, devices } from '@playwright/test'
import type { ConfigOptions } from '@nuxt/test-utils/playwright'

export default defineConfig<ConfigOptions>({
  use: {
    nuxt: {
      rootDir: fileURLToPath(new URL('.', import.meta.url))
    }
  },
  // ...
})
Read more in 전체 예제 설정 보기.

테스트 파일에서는 @nuxt/test-utils/playwright에서 직접 expecttest를 사용해야 합니다:

tests/example.test.ts
import { expect, test } from '@nuxt/test-utils/playwright'

test('test', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})

또는 테스트 파일 내에서 Nuxt 서버를 직접 설정할 수도 있습니다:

tests/example.test.ts
import { expect, test } from '@nuxt/test-utils/playwright'

test.use({
  nuxt: {
    rootDir: fileURLToPath(new URL('..', import.meta.url))
  }
})

test('test', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})