Skip to content

johannschopplich/nitro-test-utils

Repository files navigation

Nitro Test Utils

The main goal for this package is to provide a simple and easy-to-use testing environment for Nitro applications, built on top of Vitest. Use it to write tests for API routes and event handlers.

Features

  • 🚀 Automatic Nitro build (development or production mode)
  • ↪️ Reruns tests whenever Nitro source files change
  • 🥜 Run Nitro per test suite or globally
  • ✅ Seamless integration with Vitest
  • 🪝 Conditional code execution based on test mode (import.meta.test)
  • ☁️ Cloudflare Workers support with local bindings emulation (KV, D1, R2, …)
  • 📡 Familiar $fetchRaw helper similar to Nuxt test utils

Installation

Add the nitro-test-utils as well as nitro and vitest to your project with your favorite package manager:

# pnpm
pnpm add -D nitro-test-utils nitro vitest

# npm
npm install -D nitro-test-utils nitro vitest

# yarn
yarn add -D nitro-test-utils nitro vitest

Important

This package requires Nitro v3 and Vitest v4 or later.

Looking for Nitro v2 support? Use v0.11 (nitro-test-utils@^0.11).

Usage

There are two ways to set up the Nitro test environment: globally or per test suite. The global setup is useful if you want to test multiple test files against the same Nitro server. The per test suite setup is useful if you want to test different Nitro servers in different test files.

Note

By default, Nitro uses the nitro-dev preset in development mode and node-middleware in production mode. You can override this with the preset option to test against other deployment targets, such as Cloudflare Workers. See Deployment Presets for details.

Tip

The global setup is recommended for most use cases where only one Nitro application is being developed. It is more convenient to use than the per-test-suite setup because it keeps the Nitro development server running in the background during Vitest watch mode. This allows you to develop your Nitro application and write tests at the same time.

Global Setup

Getting started with the global Nitro test environment for Vitest is as simple as creating a new vitest.config.ts configuration file in your project root. Set the global option to true, which expects the Nitro source files to be located in the working directory.

import { defineConfig } from 'nitro-test-utils/config'

export default defineConfig({
  nitro: {
    global: true
  }
})

You can also pass an object to global with additional options:

  • rootDir: Path to the Nitro project root (where nitro.config.ts lives). Defaults to the Vitest working directory.
  • mode: 'development' (default) or 'production'. In development mode, Nitro automatically reloads on changes and tests re-run.
  • preset: Nitro deployment preset. Defaults to 'nitro-dev' (development) or 'node-middleware' (production). See Deployment Presets.
import { defineConfig } from 'nitro-test-utils/config'

export default defineConfig({
  nitro: {
    global: {
      rootDir: 'backend',
      mode: 'production'
    }
  },
})

Tip

Under the hood, Vitest will automatically spin up a Nitro server before running your tests and shut it down afterwards.

Write your tests in a dedicated location, e.g. a tests directory. You can use the $fetchRaw function to make requests to the Nitro server that is started by the test environment.

A simple test case could look like this:

import { $fetchRaw } from 'nitro-test-utils/e2e'
import { describe, expect, it } from 'vitest'

describe('api', () => {
  it('responds successfully', async () => {
    const { data, status } = await $fetchRaw('/api/health')

    expect(status).toBe(200)
    expect(data).toMatchSnapshot()
  })
})

Note

Whenever Nitro is rebuilt, the tests will rerun automatically (unless you have set the mode option to production in the Vitest configuration).

Per-Suite Setup

For multiple Nitro servers as part of your project, you can set up the Nitro test environment per test suite. Configure Vitest by creating a new vitest.config.ts configuration file in your project root:

import { defineConfig } from 'nitro-test-utils/config'

export default defineConfig()

Contrary to the global setup, the Nitro server is not started automatically by Vitest. Instead, you need to call the setup function in each test suite to start the Nitro server. After each test suite, the Nitro server is shut down.

The setup function accepts an options object:

  • rootDir: Path to the Nitro project root (where nitro.config.ts lives).
  • mode: 'development' (default) or 'production'.
  • preset: Nitro deployment preset. Defaults to 'nitro-dev' (development) or 'node-middleware' (production). See Deployment Presets.
import { resolve } from 'node:path'
import { $fetchRaw, setup } from 'nitro-test-utils/e2e'
import { describe, expect, it } from 'vitest'

describe('api', async () => {
  await setup({
    rootDir: resolve(import.meta.dirname, 'fixture'),
    mode: 'production'
  })

  it('responds successfully', async () => {
    const { data, status } = await $fetchRaw('/api/health')

    expect(status).toBe(200)
    expect(data).toMatchSnapshot()
  })
})

Detecting Test Environment

You can detect whether your code is running in a Nitro build during tests by checking the import.meta.test property. This is useful if you want to conditionally run code only in Nitro tests, but not in production.

To get proper TypeScript support for import.meta.test, add a triple-slash reference in your env.d.ts (or any .d.ts file included by your tsconfig.json):

/// <reference types="nitro-test-utils/env" />

Then use it in your Nitro handlers:

import { defineHandler } from 'nitro/h3'

export default defineHandler(async () => {
  // Mock data for tests
  if (import.meta.test) {
    return { foo: 'bar' }
  }

  // Your production code here
  const db = await connectToDatabase()
  return db.query()
})

Custom Test Environment Variables

You can set custom environment variables for your tests by creating a .env.test file in your Nitro project root. The variables will be loaded automatically when the Nitro server is started.

# .env.test
FOO=bar

Deployment Presets

By default, nitro-test-utils uses Node.js-compatible presets (nitro-dev for development, node-middleware for production). If your application targets a different deployment platform, you can set the preset option to match your deployment target.

Note

Non-Node presets like cloudflare-module only work in development mode, since Vitest runs inside a Node.js process. In production mode, only Node.js-compatible presets are supported.

Cloudflare Workers

To test Cloudflare-specific features like KV, D1, or R2 bindings locally, set the preset to cloudflare-module. Nitro automatically resolves this to the cloudflare-dev preset in development mode, which emulates Cloudflare bindings locally via wrangler's getPlatformProxy().

Make sure wrangler is installed as a dev dependency and a wrangler.json (or wrangler.toml) with your bindings configuration exists in your Nitro project root.

import { resolve } from 'node:path'
import { $fetchRaw, setup } from 'nitro-test-utils/e2e'
import { describe, expect, it } from 'vitest'

describe('cloudflare bindings', async () => {
  await setup({
    rootDir: resolve(import.meta.dirname, 'fixture'),
    preset: 'cloudflare-module'
  })

  it('reads from KV', async () => {
    const { data } = await $fetchRaw('/api/kv?key=test')
    expect(data.value).toBeDefined()
  })
})

Inside your Nitro handlers, access Cloudflare bindings through event.req.runtime.cloudflare.env:

import { defineHandler } from 'nitro/h3'

export default defineHandler((event) => {
  const { env } = (event.req as any).runtime.cloudflare
  return env.KV.get('my-key')
})

API Reference

$fetchRaw

The $fetchRaw function is a simple wrapper around the custom ofetch $Fetch instance created by createNitroFetch. It simplifies requesting data from your Nitro server during testing. Import the function from the nitro-test-utils/e2e module. It will dynamically use the base URL of the active test server.

$fetchRaw returns a promise that resolves with the raw response from ofetch.raw. This is useful because it allows you to access the response status code, headers, and body, even if the response failed.

Usage:

Inside a test case:

// Use `data` instead of `body` for the parsed response body
const { data, status, headers } = await $fetchRaw('/api/hello')

expect(status).toBe(200)
expect(data).toMatchSnapshot()

Type Declaration:

interface NitroFetchResponse<T> extends FetchResponse<T> {
  /** Alias for `response._data` */
  data?: T
}

function $fetchRaw<T = any, R extends ResponseType = 'json'>(
  path: string,
  options?: FetchOptions<R>
): Promise<NitroFetchResponse<MappedResponseType<R, T>>>

Tip

All additional options set in createNitroFetch apply here as well, such as ignoreResponseError set to true to prevent the function from throwing an error when the response status code is not in the range of 200-299, and retry: 0 to disable retries.

createNitroFetch

Creates a custom ofetch instance with the Nitro server URL as the base URL.

Tip

The following additional fetch options have been set as defaults:

  • ignoreResponseError: true to prevent throwing errors on non-2xx responses.
  • redirect: 'manual' to prevent automatic redirects.
  • retry: 0 to disable retries, preventing masked failures and slow test suites.
  • headers: { accept: 'application/json' } to force a JSON error response when Nitro returns an error.

Usage:

Use createNitroFetch to get a $fetch instance pre-configured for your Nitro test server – no extra setup needed:

import { createNitroFetch } from 'nitro-test-utils/e2e'
import { describe, expect, it } from 'vitest'

describe('api', () => {
  const $fetch = createNitroFetch()

  it('responds with data', async () => {
    const data = await $fetch('/api/health')
    expect(data).toEqual({ ok: true })
  })
})

Type Declaration:

function createNitroFetch(options?: FetchHooks): $Fetch

You can pass ofetch interceptors (onRequest, onResponse, onRequestError, onResponseError) to customize request/response handling while keeping the default base URL and options.

createNitroSession

Creates a session-aware fetch instance that persists cookies across requests. Useful for testing authentication flows.

Usage:

import { createNitroSession } from 'nitro-test-utils/e2e'
import { describe, expect, it } from 'vitest'

describe('auth', () => {
  it('persists session cookies', async () => {
    const session = createNitroSession()

    // Login sets a session cookie
    await session.$fetch('/api/login', { method: 'POST' })

    // Subsequent requests include the cookie automatically
    const profile = await session.$fetch('/api/profile')
    expect(profile).toEqual({ user: 'authenticated' })

    // Inspect cookies directly
    expect(session.cookies.get('session')).toBeDefined()

    // Clear cookies to simulate logout
    session.clearCookies()
  })
})

Type Declaration:

interface NitroSession {
  $fetch: $Fetch
  cookies: Map<string, string>
  clearCookies: () => void
}

function createNitroSession(): NitroSession

injectServerUrl

To get the URL of the active test server for the current test suite or global test environment, you can use the injectServerUrl function.

Usage:

import { injectServerUrl } from 'nitro-test-utils/e2e'

describe('api', () => {
  it('should log the Nitro server URL', async () => {
    const serverUrl = injectServerUrl()
    console.log(serverUrl) // http://localhost:3000
  })
})

Type Declaration:

function injectServerUrl(): string

Migrating from Nitro v2

If you are upgrading from an earlier version of nitro-test-utils that targeted Nitro v2 (nitropack), the following breaking changes apply:

  • Peer dependency: nitropack has been replaced by nitro (v3).
  • Renamed types: TestOptionsNitroTestOptions, TestContextNitroTestContext, TestServerNitroTestServer, TestFetchResponseNitroFetchResponse.
  • Removed deprecated config fields: The top-level mode and rootDir options in the Vitest nitro config have been removed. Use nitro.global.mode and nitro.global.rootDir instead.

For Nitro v3 API changes (handler definitions, error handling, request body, presets, etc.), see the official Nitro v3 migration guide.

License

MIT License © 2024-PRESENT Johann Schopplich

About

🧪 Testing environment and utilities for Nitro

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors