测试
一些关于测试使用 `nuqs` 的组件的提示
自 nuqs 2 起,您可以单元测试使用 useQueryState(s) 钩子的组件,而无需模拟任何内容,通过使用专用的测试适配器,该适配器将便于设置您的测试(带有初始搜索参数)和在作用于您的组件时断言 URL 更改。
使用 React Testing Library 测试钩子
当使用 React Testing Library 的 renderHook 函数测试依赖 nuqs 的 useQueryState(s) 的钩子时,您可以使用 withNuqsTestingAdapter 获取一个包装组件传递给 renderHook 调用:
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'
const { result } = renderHook(() => useTheHookToTest(), {
wrapper: withNuqsTestingAdapter({
searchParams: { count: "42" },
}),
})使用 Vitest 测试组件
这里是一个使用 Vitest 和 Testing Library 测试渲染计数器的按钮的示例:
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. 通过传递初始搜索参数 / 查询字符串来设置测试:
wrapper: withNuqsTestingAdapter({ searchParams: '?count=42', onUrlUpdate })
})
// 2. 作用
const button = screen.getByRole('button')
await user.click(button)
// 3. 断言状态和(模拟的)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')
})import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { withNuqsTestingAdapter, type OnUrlUpdateFunction } 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<OnUrlUpdateFunction>()
render(<CounterButton />, {
// 1. 通过传递初始搜索参数 / 查询字符串来设置测试:
wrapper: withNuqsTestingAdapter({ searchParams: '?count=42', onUrlUpdate })
})
// 2. 作用
const button = screen.getByRole('button')
await user.click(button)
// 3. 断言状态和(模拟的)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')
})有关更多测试相关讨论,请参见 issue #259。
Jest 和 ESM
由于 nuqs 2 是一个 仅 ESM 包,要使其与 Jest 配合使用,您需要跳过一些障碍。这是从 Jest ESM 指南 中提取的。
- 在您的 jest.config.ts 文件中添加以下选项:
const config: Config = {
// <Other options here>
extensionsToTreatAsEsm: [".ts", ".tsx"],
transform: {}
};- 将您的测试命令更改为包含
--experimental-vm-modules标志:
{
"scripts": {
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
}
}相应地为 Windows 调整,使用 cross-env。
API
withNuqsTestingAdapter 接受以下参数:
searchParams:用于测试的初始搜索参数。这些可以是查询字符串、URLSearchParams对象或具有字符串值的记录对象。
withNuqsTestingAdapter({
searchParams: '?q=hello&limit=10'
})
withNuqsTestingAdapter({
searchParams: new URLSearchParams('?q=hello&limit=10')
})
withNuqsTestingAdapter({
searchParams: {
q: 'hello',
limit: '10' // 值是序列化的字符串
}
})-
onUrlUpdate:当组件更新 URL 时调用的函数。它接收一个对象,其中包含:- 新的搜索参数,作为
URLSearchParams的实例 - 新的渲染查询字符串(为了方便)
- 用于更新 URL 的选项。
- 新的搜索参数,作为
-
hasMemory:默认情况下,测试适配器是不可变的,意味着它始终使用初始搜索参数作为 URL 更新的基础。这鼓励在单个测试中测试行为单元。
要使其像框架适配器一样行为(这些适配器确实在 URL 中存储更新),设置 hasMemory: true,以便后续更新基于先前状态构建。此内存是每个适配器实例的,因此在测试之间隔离,但在同一适配器下的组件之间共享。
🧪 内部/高级选项
-
rateLimitFactor。默认情况下,测试时禁用限速,因为它可能导致意外行为。将此设置为 1 将启用与生产中相同的限速因子。 -
resetUrlUpdateQueueOnMount:在运行测试前清除 URL 更新队列。这是默认的true以隔离测试,但您可以将它设置为false以在渲染之间保留 URL 更新队列,并更接近生产行为。
NuqsTestingAdapter
withNuqsTestingAdapter 函数是一个包装组件工厂函数,它使用 NuqsTestingAdapter 包装子组件,但您也可以直接使用它:
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
<NuqsTestingAdapter>
<ComponentsUsingNuqs/>
</NuqsTestingAdapter>它接受与传递给 withNuqsTestingAdapter 的参数相同的 props。
测试自定义解析器
如果您使用 createParser 创建自定义解析器,您可能想要测试它们。
解析器应:
- 为
parse、serialize和eq定义纯函数。 - 是双射的:
parse(serialize(x)) === x和serialize(parse(x)) === x。
为了帮助测试双射性,您可以使用 nuqs/testing 中定义的助手:
import {
isParserBijective,
testParseThenSerialize,
testSerializeThenParse
} from 'nuqs/testing'
it('是双射的', () => {
// 通过的测试返回 true
expect(isParserBijective(parseAsInteger, '42', 42)).toBe(true)
// 失败的测试会抛出错误
expect(() => isParserBijective(parseAsInteger, '42', 47)).toThrowError()
// 您也可以单独测试任一侧:
expect(testParseThenSerialize(parseAsInteger, '42')).toBe(true)
expect(testSerializeThenParse(parseAsInteger, 42)).toBe(true)
// 如果测试失败,这些也会抛出错误,从而更容易隔离哪一侧失败:
expect(() => testParseThenSerialize(parseAsInteger, 'not a number')).toThrowError()
expect(() => testSerializeThenParse(parseAsInteger, NaN)).toThrowError()
})