首页 > 解决方案 > react-js 测试库无法测试 useState 钩子和异步 axios 登录功能

问题描述

我似乎无法让我的 reactjs 登录组件工作的测试。我只使用 react-testing-library 而不是 Enzyme。

我希望能够测试 3 件事:

  1. 单击一次登录并且axios ajax 调用使用模拟的 ajax
  2. 我可以使用 useState 设置一个值并检查该值是否已设置
  3. 成功登录后呈现的页面有文本欢迎

以上都不起作用。我一直在解决不同的问题,例如这个expect(signIn).toHaveBeenCalledTimes(1)返回:

    Expected number of calls: 1
    Received number of calls: 0

其他时候它会抛出错误:无法找到给定节点的“窗口”对象。为了这:

const button = screen.getByRole('button', {name: /submit/i})

这是 Login.test.js 的样子

/** 
 * @jest-environment jsdom
*/

import React from 'react';
import { screen, render, waitFor, fireEvent, getAllByLabelText } from '@testing-library/react';
import {renderHook} from '@testing-library/react-hooks'

import userEvent from '@testing-library/user-event';
import { act } from "react-dom/test-utils";

import Login from "../components/auth/login"
import Index from "../components/index"
import {userContext} from "../utils/context/UserContext"
import axios from 'axios';

const user = {user: {} }
const isLoggedIn = false
const setIsLoggedIn = jest.fn()
const setUser = jest.fn()
const setState = jest.fn();

jest.mock('axios');

describe("Login", () => {

  afterEach(() => {
    jest.clearAllMocks();
  });

  test('ajax login', async () => {

    const {container} = render(
      <userContext.Provider value={ {user, isLoggedIn, setIsLoggedIn, setUser} } >
        <Login />
      </userContext.Provider>
    )

   axios.post.mockImplementationOnce(() => Promise.resolve({
      status: 200,
      data: {user: {name: 'Rony', email: 'rony@example.com' } }
   }))

   setIsLoggedIn.mockReturnValue(true)
   const signIn = jest.spyOn(React, "useState")
   signIn.mockImplementation(isLoggedIn => [isLoggedIn, setIsLoggedIn]);

   const email = container.querySelector('input[name=email]')
   const password = container.querySelector('input[name=password]')
   const button = screen.getByRole('button', {name: /submit/i})

   await act(async () => {
     userEvent.type('email', 'abc@yahoo.com')
     userEvent.type('password', 'abcddef')
     userEvent.click(button)
   });

   expect(signIn).toHaveBeenCalledTimes(1)
   expect(setIsLoggedIn).toEqual(true);
   expect(screen.getByText(/welcome/i)).resolves.toBeInTheDocument
    
  })
})

登录组件如下图所示:

import React, {useState, useEffect, useContext} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'js-cookie';
import axios from 'axios'
import {userContext} from "../../utils/context/UserContext"

export const Login = (props) => {
  const {user, setIsLoggedIn, isLoggedIn, setUser} = useContext(userContext)

  const token = Cookies.get('csrf_token') || props.csrf_token || ''

  const[loginData, setLoginData] = useState({email: '', password: '' })

  const[csrf_token, setCsrfToken] = useState('')

  useEffect(() => {
    setCsrfToken(token)
  }, [])

  const handleChange = (event) => {
    const {name, value} = event.target
    setLoginData({...loginData, [name]: value})
  }

  const signIn = (event) => {
    event.preventDefault();
    let headers = {
      headers: {'Content-Type': 'application/json',
      'X-CSRF-TOKEN': csrf_token
      }
    }

    let url = "/api/v1/sessions"
  
    axios.post(url, {user: loginData}, headers)
      .then(response => {
        setLoginData({email: '', password: ''})

        setIsLoggedIn(true)
        setUser(response.data.user)

    })
    .catch(function (error) {
      setIsLoggedIn(false)
    })
 
  }

  return(
    <React.Fragment>
      <div> </div>
      <form onSubmit={signIn}>
        <h3>Login</h3>

        <div className="mb-3 form-group col-xs-4">
          <label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
          <input type="email" name="email" className="form-control" id="exampleInputEmail1" 
              value={loginData.email} onChange={handleChange} aria-describedby="emailHelp" />
          <div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
        </div>

        <div className="mb-3">
          <label htmlFor="exampleInputPassword1" className="form-label">Password</label>
          <input type="password" name="password" className="form-control" id="exampleInputPassword1"
            value={loginData.password} onChange={handleChange} />
        </div>

        <div className="mb-3 form-check">
          <input type="checkbox" className="form-check-input" id="exampleCheck1" />
          <label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
        </div>
        
        <button name="submit" type="submit" className="btn btn-primary" onClick={signIn} >Submit</button>
      </form>
    </React.Fragment>
  )
}

UserContext.js

import React from 'react';
export const userContext = React.createContext(undefined)

index.js

import React, {useState, useEffect, useContext} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'js-cookie';
import axios from 'axios'
import {userContext} from "../../utils/context/UserContext"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Login from "../components/auth/login";

export default const Index = (props) => {

  let form_token = Cookies.get('csrf_token') || ''
  let check_login = props.isLoggedIn || false
  let check_user = props.user || ""

  const[isLoggedIn, setIsLoggedIn] = useState(check_login)
  const[user, setUser] = useState(check_user)
  const[isError, setIsError] = useState('')

  useEffect(() => {
    setCsrfToken(form_token)
  }, [isLoggedIn, csrf_token, user])

  return (
      <userContext.Provider value={{user, setUser, isLoggedIn, setIsLoggedIn}}>
      <Router>
      <React.Fragment>
        <div>
          <Switch>
            <Route exact path='/'>
               
            </Route>

            <Route path='/login'>
              <Login />
            </Route>

          </Switch>
        </div>
        </React.Fragment>
      </Router>
      </userContext.Provider>
  )
}

标签: javascriptreactjsaxiosuse-statereact-testing-library

解决方案


你不应该模拟 React 的 useState 函数,并且你不能测试重定向,因为你在测试中只渲染了 Login 组件。

除此之外,我认为您不能模拟功能组件(signIn)中定义的功能,即使有可能您也不应该测试内部实现,而只能测试真实用户应该看到的内容:在这里,您可以测试输入之后有正确的值userEvent.type(...)

只需删除useStatesignIn模拟,然后在您的登录组件中添加成功登录后的重定向。然后你只需要输入用户信息,点击提交按钮(就像你已经做的那样),然后你可以检查 history.push 是否被正确调用(参见最简单的测试 react-router's Link with @testing-library/react)。


推荐阅读