如何在Storybook中断言nextRouter.push动作已触发?
问题解决:Storybook中断言Next.js router.push调用失败的修复
错误原因
你使用action('nextRouter.push')创建的是一个全新的action实例,并非Storybook用来记录路由调用的那个mock/spy函数,因此Jest的toHaveBeenCalledWith断言无法识别它,导致报错。
解决方案一:复用Storybook自动注册的action spy
@storybook/nextjs会自动将nextRouter的方法注册为Storybook actions,这些action的spy实例可以通过Storybook的内部通道获取:
import { within, fireEvent } from '@storybook/testing-library'; import { expect } from '@storybook/jest'; export const Default: Story = { play: async (args) => { const canvas = within(args.canvasElement); // 触发组件变更事件 fireEvent.change(canvas.getByTestId('my-component'), { target: { value: 'AMSTERDAM' }, }); // 获取Storybook的actions通信通道 const channel = window.__STORYBOOK_ADDONS_CHANNEL__; // 取出nextRouter.push对应的spy函数 const routerPushSpy = channel.getAction('nextRouter.push'); // 执行断言 expect(routerPushSpy).toHaveBeenCalledWith('/stays/amsterdam'); }, };
解决方案二:手动mock useRouter钩子(更适合CI环境)
直接在Story中mock Next.js的useRouter钩子,完全控制路由行为,避免依赖Storybook内部API,稳定性更高:
import { within, fireEvent } from '@storybook/testing-library'; import { expect, jest } from '@storybook/jest'; import { useRouter } from 'next/router'; // 全局mock useRouter jest.mock('next/router', () => ({ useRouter: jest.fn(), })); export const Default: Story = { beforeEach: () => { // 每次用例前重置mock,避免用例间污染 const mockPush = jest.fn(); (useRouter as jest.Mock).mockReturnValue({ push: mockPush, // 按需添加其他router属性(如pathname、query等) }); }, play: async (args) => { const canvas = within(args.canvasElement); fireEvent.change(canvas.getByTestId('my-component'), { target: { value: 'AMSTERDAM' }, }); // 获取mock的push方法并断言 const mockPush = (useRouter as jest.Mock).mock.results[0].value.push; expect(mockPush).toHaveBeenCalledWith('/stays/amsterdam'); }, };
方案对比
- 方案一:无需额外mock,复用Storybook自动生成的路由模拟,但依赖Storybook内部实现,兼容性可能随版本变化。
- 方案二:完全独立,不依赖Storybook内部机制,断言逻辑更清晰,适合CI环境长期运行。
内容的提问来源于stack exchange,提问作者viam0Zah
相关产品推荐
相关产品推荐

