【问题标题】:Mocking Promises makes them return undefined模拟 Promises 使它们返回未定义
【发布时间】:2022-01-03 18:05:33
【问题描述】:

我正在尝试在我的 React 组件上运行一些开玩笑的测试,更具体地说是针对我的 LoginForm 组件。

我有一个调用 auth api 进行登录的函数:

import axios from 'axios'

const doLogin = (username: string, password: string) => {
  return axios.post('api/auth', {
    username,
    password
  })
}

export default { doLogin }

很简单,只是返回一个 axios 响应的 Promise。

我已经在我的LoginPage.tsx 中实现了它:(submitLogin):

import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import SwitchLogo from '../assets/switch-logo.png'
import { Button, TextBox } from '../components/Form'
import userApi from '../api/user'

const LoginPage = () => {
  const { doLogin } = userApi
  const [username, setUsername] = useState<string>('')
  const [password, setPassword] = useState<string>('')

  const [loading, setLoading] = useState<boolean>(false)
  const [error, setError] = useState<string | undefined>(undefined)

  const onUsernameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setUsername(e.currentTarget.value)
  }

  const onPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setPassword(e.currentTarget.value)
  }

  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (!username || !password) {
      setError('Please fill in login details.')
      return
    }
    submitLogin()
  }

  const submitLogin = () => {
    setError(undefined)
    setLoading(true)
    doLogin(username, password).then((response) => {
      console.log(JSON.stringify(response))
    }).catch((err) => {
      setError(err.response.data.error)
    }).finally(() => setLoading(false))
  }

  return (
    <div className="flex h-screen bg-gray-50">
      <div className="m-auto w-full sm:w-5/6 lg:w-2/4 xl:w-1/3">
        <div className="flex justify-center"><img src={SwitchLogo} className="w-32 h-32" alt="switch-logo" /></div>
        <form onSubmit={onSubmit} data-testid='form'>
          <div className="bg-white shadow-lg drop-shadow-lg rounded px-8 pt-6 pb-8 mb-4 flex flex-col gap-4">
            <div className='flex flex-col gap-2 text-center'>
              <p className="text-4xl font-bold">Sign in</p>
              <p className='text-sm'>Please sign in to access all features.</p>
            </div>
            <div className="divider m-0" />
            <div>
              <TextBox id="username" type="text" placeholder="Username" required value={username} onChange={onUsernameChange} data-testid='username' />
            </div>
            <div>
              <TextBox id="password" type="password" placeholder="Password" required value={password} onChange={onPasswordChange} data-testid='password' />
            </div>
            <Button colour='red' loading={loading} data-testid='submit'>{!loading && 'Sign in'}</Button>
            <Button colour='sky' type='button' outline><Link to='/register'>Register new account</Link></Button>
          </div>
          {error && (<div className="alert alert-error">
            <div className='mx-auto'>
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="w-6 h-6 mx-2 stroke-current">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"></path>
              </svg>
              <label>{error}</label>
            </div>
          </div>)}
        </form>
      </div>
    </div>
  )
}

export default LoginPage

注意doLogin 被调用,然后用.then 处理。当我运行应用程序时,这一切正常。

问题是当我尝试测试我的登录页面时。

我有登录页面的规范文件,我在其中模拟 axios 中的 post 函数,然后返回具有最少数据 (LoginPage.spec.tsx) 的基本 AxiosResponse 对象:

import { fireEvent, getByTestId, getByText, render } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import LoginPage from './LoginPage'
import axios, { AxiosResponse } from 'axios'

jest.mock('axios')

const mockedAxios = axios as jest.Mocked<typeof axios>

const base: AxiosResponse<any, any> = {
  data: { test: 123 },
  status: 200,
  statusText: 'OK',
  headers: {},
  config: {}
}

mockedAxios.post.mockResolvedValue(base)

describe('Sign in form', () => {
  beforeEach(() => {
    render(
      <BrowserRouter>
        <LoginPage />
      </BrowserRouter >
    )
  })

  it('Renders', () => {
    expect(getByText(document.body, /Please sign in to access all features./i)).toBeInTheDocument()
  })

  it('Does not submit when no data filled in', () => {
    fireEvent.click(getByTestId(document.body, 'submit'))
    expect(getByText(document.body, /Please fill in login details/i)).toBeInTheDocument()
    expect(mockedAxios.post).not.toHaveBeenCalled()
  })

  it('Submits when data filled in', () => {
    fireEvent.change(getByTestId(document.body, 'username'), { target: { value: 'testUser' } })
    fireEvent.change(getByTestId(document.body, 'password'), { target: { value: 'testPassword' } })
    fireEvent.click(getByTestId(document.body, 'submit'))
    expect(mockedAxios.post).toHaveBeenCalledTimes(1)
  })
})

发生的情况是,当测试运行时,它调用submitLogin,然后尝试调用doLogin().then,但显然.then 不存在:

Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'then')]

【问题讨论】:

  • 只要确保 doLogin 返回一个带有 then 方法的对象。 jestjs.io/docs/manual-mocks 可能会有所帮助
  • @evolutionxbox 我不明白。确实如此。一个promise 应该包含一个.then 方法,这就是doLogin 返回的……一个Promise。
  • 不按错误。它认为doLogin 正在返回未定义。
  • 是的,我知道,我只是不明白为什么。好几个小时我都在摸不着头脑。

标签: javascript reactjs typescript jestjs


【解决方案1】:

我终于明白了。问题是因为我在测试之外模拟了函数,但是在每次测试之后,jest 都会重置模拟——这意味着我的函数不再被模拟。我只需将mockedAxios.post.mockResolvedValue(base) 放入beforeEach 块中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    • 2018-05-12
    • 1970-01-01
    • 2021-09-11
    • 1970-01-01
    相关资源
    最近更新 更多