javascript - react-js 测试库无法测试 useState 钩子和异步 axios 登录功能
问题描述
我似乎无法让我的 reactjs 登录组件工作的测试。我只使用 react-testing-library 而不是 Enzyme。
我希望能够测试 3 件事:
- 单击一次登录并且axios ajax 调用使用模拟的 ajax
- 我可以使用 useState 设置一个值并检查该值是否已设置
- 成功登录后呈现的页面有文本欢迎
以上都不起作用。我一直在解决不同的问题,例如这个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>
)
}
解决方案
你不应该模拟 React 的 useState 函数,并且你不能测试重定向,因为你在测试中只渲染了 Login 组件。
除此之外,我认为您不能模拟功能组件(signIn
)中定义的功能,即使有可能您也不应该测试内部实现,而只能测试真实用户应该看到的内容:在这里,您可以测试输入之后有正确的值userEvent.type(...)
。
只需删除useState
和signIn
模拟,然后在您的登录组件中添加成功登录后的重定向。然后你只需要输入用户信息,点击提交按钮(就像你已经做的那样),然后你可以检查 history.push 是否被正确调用(参见最简单的测试 react-router's Link with @testing-library/react)。
推荐阅读
- matlab - 如何在matlab中绘制体积切片
- python - 如何使用 Oauth2 进行会话并发出可重复的请求?
- javascript - Jsxgraph如何嵌入Katex而不是Mathjs
- sql - 根据某种顺序,为源表中的每一行从目标表中获取一行
- vue.js - VueJS/vuex 应用设计题——如何用getter初始化本地数据
- go - 构建 go 项目时忽略 ldflags
- jsf - 下载文件后重新渲染组件
- angular - websocket 传输在带有 NestJS(仅长轮询)的谷歌云应用程序引擎标准上不起作用,在 flex 环境中工作
- julia - 在 Julia 中可视化一维随机游走
- node.js - 我在 ytdl-core 包上收到 404 错误