Announcing nuqs version 2
nuqs

Testing

Some tips on testing components that use `nuqs`

Since nuqs 2, you can unit-test components that use useQueryState(s) hooks without needing to mock anything, by using a dedicated testing adapter that will facilitate setting up your tests (with initial search params) and asserting on URL changes when acting on your components.

Testing hooks with React Testing Library

When testing hooks that rely on nuqs’ useQueryState(s) with React Testing Library’s renderHook function, you can use withNuqsTestingAdapter to get a wrapper component to pass to the renderHook call:

import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'
 
const { result } = renderHook(() => useTheHookToTest(), {
  wrapper: withNuqsTestingAdapter({
    searchParams: { count: "42" },
  }),
})

Testing components with Vitest

Here is an example for Vitest and Testing Library to test a button rendering a counter:

counter-button.test.tsx

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { withNuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'
import { describe, expect, it, vi } from 'vitest'
import { CounterButton } from './counter-button'
 
it('should increment the count when clicked', async () => {
  const user = userEvent.setup()
  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()
  render(<CounterButton />, {
    // 1. Setup the test by passing initial search params / querystring:
    wrapper: withNuqsTestingAdapter({ searchParams: '?count=42', onUrlUpdate })
  })
  // 2. Act
  const button = screen.getByRole('button')
  await user.click(button)
  // 3. Assert changes in the state and in the (mocked) URL
  expect(button).toHaveTextContent('count is 43')
  expect(onUrlUpdate).toHaveBeenCalledOnce()
  const event = onUrlUpdate.mock.calls[0][0]!
  expect(event.queryString).toBe('?count=43')
  expect(event.searchParams.get('count')).toBe('43')
  expect(event.options.history).toBe('push')
})

See issue #259 for more testing-related discussions.

Jest and ESM

Since nuqs 2 is an ESM-only package, there are a few hoops you need to jump through to make it work with Jest. This is extracted from the Jest ESM guide.

  1. Add the following options to your jest.config.ts file:
jest.config.ts
const config: Config = {
  // <Other options here>

  extensionsToTreatAsEsm: [".ts", ".tsx"],
  transform: {}
};
  1. Change your test command to include the --experimental-vm-modules flag:
package.json

{
  "scripts": {
    "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
  }
}

Adapt accordingly for Windows with cross-env.

API

withNuqsTestingAdapter accepts the following arguments:

  • searchParams: The initial search params to use for the test. These can be a query string, a URLSearchParams object or a record object with string values.
withNuqsTestingAdapter({
  searchParams: '?q=hello&limit=10'
})
 
withNuqsTestingAdapter({
  searchParams: new URLSearchParams('?q=hello&limit=10')
})
 
withNuqsTestingAdapter({
  searchParams: {
    q: 'hello',
    limit: '10' // Values are serialized strings
  }
})
  • onUrlUpdate: a function that will be called when the URL is updated by the component. It receives an object with:
    • the new search params as an instance of URLSearchParams
    • the new renderd query string (for convenience)
    • the options used to update the URL.
🧪 Internal/advanced options
  • rateLimitFactor. By default, rate limiting is disabled when testing, as it can lead to unexpected behaviours. Setting this to 1 will enable rate limiting with the same factor as in production.

  • resetUrlUpdateQueueOnMount: clear the URL update queue before running the test. This is true by default to isolate tests, but you can set it to false to keep the URL update queue between renders and match the production behaviour more closely.

NuqsTestingAdapter

The withNuqsTestingAdapter function is a wrapper component factory function wraps children with a NuqsTestingAdapter, but you can also use it directly:


import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
 
<NuqsTestingAdapter>
  <ComponentsUsingNuqs/>
</NuqsTestingAdapter>

It takes the same props as the arguments you can pass to withNuqsTestingAdapter.

On this page