You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

额外说明

  1. 如果你想要用spyOn的方式来监听,可以这样修改:
import * as nextAuthReact from 'next-auth/react'

// ...

// 在测试用例里获取spy
const signInSpy = vi.spyOn(nextAuthReact, 'signIn')

// 断言时用expect(signInSpy)...

这样也能确保你监听的是组件实际调用的那个函数引用。

  1. 另外注意describe函数不需要加async,只有测试用例(it/test)内部有异步操作时才需要加async。

备注:内容来源于stack exchange,提问作者Anderson Bernal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 14:33:08