npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nitro-test-utils

v2.0.2

Published

Testing toolkit for Nitro full-stack servers

Readme

Nitro Test Utils

A simple and easy-to-use testing toolkit for Nitro servers, built on top of Vitest. Use it to write tests for your 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 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] Requires Nitro v3 and Vitest v4 or later.

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

Setup

There are two ways to set up the test environment: globally (one Nitro server for all tests) or per test suite (different servers per test file).

[!NOTE] If you are using Nitro as a Vite plugin (nitro/vite), no additional configuration is needed. Since nitro.config.ts is required even in Vite projects, nitro-test-utils loads it directly and creates a standalone Nitro server for testing.

Global Setup (Recommended)

Getting started with the global setup is as simple as creating a vitest.config.ts in your project root. Pass { global: true } as the second argument to enable a shared Nitro server for all tests:

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

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

You can also pass an options object to global with additional options like rootDir, mode, and preset:

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

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

Now, write your tests in a dedicated 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()
  })
})

[!TIP] The global setup is recommended for most use cases. It keeps the Nitro dev server running in the background during Vitest watch mode, so you can develop and test at the same time. Whenever Nitro rebuilds, tests rerun automatically.

Per-Suite Setup

If you have multiple Nitro servers as part of your project, you can set up the test environment per test suite instead. The Vitest config needs no nitro options:

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

export default defineConfig()

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

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()
  })
})

Configuration

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 starts:

# .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.

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.

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

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')
})

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 tests, but not in production:

import { defineHandler } from 'nitro/h3'

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

  const db = await connectToDatabase()
  return db.query()
})

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" />

API Reference

defineConfig

Configures Vitest for Nitro testing. Accepts an optional Vite/Vitest config as the first argument and Nitro test options as the second.

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

export default defineConfig(
  // Vite/Vitest config (optional)
  { test: { dir: './tests' } },
  // Nitro test options (optional)
  { global: true }
)

Type Declaration:

function defineConfig(
  userConfig?: UserConfig,
  testConfig?: NitroTestConfig
): Promise<UserConfig>

interface NitroTestConfig {
  /** Watch Nitro source files and rerun tests on changes. Default: `true`. */
  rerunOnSourceChanges?: boolean
  /** Enable a global Nitro server for all tests. Set to `true` for defaults, or pass options. */
  global?: boolean | NitroTestOptions
}

interface NitroTestOptions {
  /** Path to the Nitro project root. Default: Vitest working directory. */
  rootDir?: string
  /** `'development'` (default) or `'production'`. */
  mode?: 'development' | 'production'
  /** Nitro deployment preset. */
  preset?: string
}

setup

Starts a Nitro server for the current test suite. Used with the per-suite setup. The server is automatically stopped after the suite completes.

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

await setup({ rootDir: './fixture' })

Type Declaration:

function setup(options?: NitroTestOptions): Promise<void>

See NitroTestOptions for available options.

$fetchRaw

A simple wrapper around the custom ofetch instance created by createNitroFetch. It simplifies requesting data from your Nitro server during testing and 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.

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

describe('api', () => {
  it('responds with data', async () => {
    // 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()
  })
})

[!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.

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>>>

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.

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.

createNitroSession

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

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.

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

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

Type Declaration:

function injectServerUrl(): string

Migration

From v1 to v2

The nitro key on Vite's UserConfig has been replaced with a second argument to defineConfig. This resolves a type collision with Nitro's own Vite plugin (nitro/vite), which claims the same nitro key.

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

-export default defineConfig({
-  nitro: {
-    global: true,
-  },
-})
+export default defineConfig({}, {
+  global: true,
+})

With custom options:

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

From v0.x (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 replaced by nitro (v3).
  • Renamed types: TestOptionsNitroTestOptions, TestContextNitroTestContext, TestServerNitroTestServer, TestFetchResponseNitroFetchResponse.

For Nitro v3 API changes, see the official Nitro v3 migration guide.

License

MIT License (c) 2024-PRESENT Johann Schopplich