Vitest测试React登录页面时signIn Spy未被调用的问题求助
Vitest测试React登录页面时signIn Spy未被调用的问题求助
大家好,我最近在用Vitest测试我的Next.js登录页面组件,遇到了一个搞不定的问题——明明登录逻辑里调用了next-auth的signIn函数,但测试里的spy始终检测不到它被调用,一直报错说调用次数为0。下面是我的代码和问题详情,麻烦各位大佬帮忙看看哪里出问题了!
我的登录页面核心代码
const LoginPage = () => { const [error, setError] = useState<string | null>(null); const router = useRouter() const { handleSubmit, register, reset } = useForm<LoginFormData>() const onSubmit: SubmitHandler<LoginFormData> = async (data) => { const response = await signIn('credentials', { ...data, redirect: false, }) if (response && response.ok) { setError(null) reset() router.replace('/billboard') return } if (response && response.error) setError(response.error) } return ( <div> <Image src='/images/logo.png' alt="hero" width={160} height={160} className='absolute z-20 top-5 left-5' /> {!!error && <p> <AiOutlineFire /> {error} </p>} <form onSubmit={handleSubmit(onSubmit)}> <h2 className="text-2xl font-semibold">Login</h2> <div> <input type="text" {...register('email')} aria-label='email' placeholder="Email address or phone number" /> <input type="password" {...register('password')} aria-label='password' placeholder="Password" /> </div> <div> <button type="submit" aria-label='submit'> Login </button> <div> <FcGoogle size={35} /> <VscGithub size={35} /> </div> <p> <span>First time using Netflix? </span> <Link href='/auth/signup' className="font-bold"> Create a Netflix account. </Link> </p> </div> </form> </div> ); } export default LoginPage;
我的测试代码
import { render, screen } from '@testing-library/react' import LoginPage from './LoginPage' import userEvent from '@testing-library/user-event' vi.mock('next/router', () => require('next-router-mock')) vi.mock('next-auth/react', () => ({ signIn: vi.fn() })) vi.spyOn(require('next-auth/react'), 'signIn') describe('LoginPage', async () => { beforeEach(() => vi.clearAllMocks()) it('should submit a non existing email', async () => { const signIn = vi.fn() render(<LoginPage />) const emailInput = screen.getByLabelText('email') const passwordInput = screen.getByLabelText('password') const submitButton = screen.getByLabelText('submit') const email = 'anderson@gmail' const password = 'password123' await userEvent.type(emailInput, email) await userEvent.type(passwordInput, password) await userEvent.click(submitButton) expect(signIn).toHaveBeenCalledWith('credentials', { email, password, redirect: false, }) }) })
测试报错信息
src/common/components/pages/LoginPage/LoginPage.test.tsx > LoginPage > should submit a non existing email AssertionError: expected "spy" to be called with arguments: [ 'credentials', …(1) ] Received: Number of calls: 0 ❯ src/common/components/pages/LoginPage/LoginPage.test.tsx:32:24 30| await userEvent.click(submitButton) 31| 32| expect(signIn).toHaveBeenCalledWith('credentials', { | ^ 33| email, password, 34| redirect: false,
我已经查了不少资料,但大部分都是针对Jest的方案,虽然Vitest和Jest语法很像,但试了都不管用。我就是想验证登录提交时signIn函数有没有被正确调用,这样才能继续写后续的测试逻辑,有没有大佬能指点一下?
问题分析与解决方案
其实问题出在测试用例里的变量覆盖上:
你在测试用例内部重新声明了const signIn = vi.fn(),这相当于创建了一个全新的空mock函数,但你的登录组件实际调用的是你通过vi.mock('next-auth/react')全局mock的那个signIn。你最后断言的是这个本地的空函数,自然会显示调用次数为0。
修复后的测试代码
import { render, screen } from '@testing-library/react' import LoginPage from './LoginPage' import userEvent from '@testing-library/user-event' // 直接导入被mock的signIn函数 import { signIn } from 'next-auth/react' vi.mock('next/router', () => require('next-router-mock')) vi.mock('next-auth/react', () => ({ signIn: vi.fn() })) // describe不需要加async describe('LoginPage', () => { beforeEach(() => vi.clearAllMocks()) it('should submit a non existing email', async () => { // 删掉本地的signIn声明 render(<LoginPage />) const emailInput = screen.getByLabelText('email') const passwordInput = screen.getByLabelText('password') const submitButton = screen.getByLabelText('submit') const email = 'anderson@gmail' const password = 'password123' await userEvent.type(emailInput, email) await userEvent.type(passwordInput, password) await userEvent.click(submitButton) // 断言导入的那个被mock的signIn expect(signIn).toHaveBeenCalledWith('credentials', { email, password, redirect: false, }) }) })
额外说明
- 如果你想要用
spyOn的方式来监听,可以这样修改:
import * as nextAuthReact from 'next-auth/react' // ... // 在测试用例里获取spy const signInSpy = vi.spyOn(nextAuthReact, 'signIn') // 断言时用expect(signInSpy)...
这样也能确保你监听的是组件实际调用的那个函数引用。
- 另外注意
describe函数不需要加async,只有测试用例(it/test)内部有异步操作时才需要加async。
备注:内容来源于stack exchange,提问作者Anderson Bernal
相关产品推荐
相关产品推荐

