如何使用React,Redux和Typescript正确设置环境以进行测试?
我在设置用于使用Redux和Typescript的React应用程序的环境时有问题,
这是我的测试:
import React from 'react';
import { render, screen } from '@testing-library/react'
import Login from './Login'
import { Provider } from 'react-redux'
import configureStore from 'redux-mock-store'
import { MemoryRouter } from 'react-router-dom';
import {createMemoryHistory} from 'history'
describe('Rendering', ()=>{
const initialState = {
user: {
id:'',
name: "",
email: ''
},
loading: false,
error:{
message: ''
}
}
const mockStore = configureStore()
let store
test('renders initial screen', () => {
const history = createMemoryHistory()
store = mockStore(initialState)
render(
<Provider store={store}>
<MemoryRouter history={history}>
<Login />
</MemoryRouter>
</Provider>
)
const loginScreenMessage = screen.getByRole('heading', { name:/login/i } );
expect(loginScreenMessage).toBeInTheDocument();
})
})
这是登录组件:
import React, {useState, useEffect} from 'react'
import { useAppDispatch, useAppSelector } from 'store/hooks';
import {useNavigate} from 'react-router-dom';
import {loginUserAction} from 'store/actions/AuthAction'
import ValidationErrors from 'components/ValidationErrors'
function Login() {
const dispatch = useAppDispatch()
const navigate = useNavigate()
const loading = useAppSelector(state =>state.auth.loading)
const [form , setForm] = useState({
email : "",
password : ""
})
const error = useAppSelector(state =>state.auth.error)
useEffect(()=>{
dispatch(ErrorAction({message:''})
}, [])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setForm({
...form,
[e.target.name] : e.target.value
})
}
const submitLogin = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
try{
await dispatch(loginUserAction(form))
navigate('/home')
}catch{
//
}
}
return (
<div className="login" data-test="component-login">
<div className="w-full mx-auto sm:w-8/12 md:w-6/12 lg:w-4/12 bg-white pt-5 px-10">
<AiOutlineTwitter className="text-5xl text-twitter" />
<h1 className="text-4xl font-bold my-10 ">Login</h1>
{ error.message &&
<ValidationErrors errors={error} />
}
<form onSubmit={submitLogin}>
<div className="special-label-holder relative mt-5">
<input
type="text"
id="email"
name="email"
placeholder=" "
className="input-form"
value={form.email}
onChange={handleChange}
/>
<label
className="text-lg h-full left-2 text-gray-500 top-3 overflow-hidden pointer-events-none absolute transition-all duration-200 ease-in-out"
htmlFor="email"
>
Email, phone or username
</label>
</div>
<div className="special-label-holder relative mt-5">
<input
id="password"
type="password"
name="password"
placeholder=" "
className="input-form"
value={form.password}
onChange={handleChange}
/>
<label
className="text-lg h-full left-2 text-gray-500 top-3 overflow-hidden pointer-events-none absolute transition-all duration-200 ease-in-out"
htmlFor="password"
>
Password
</label>
</div>
<button disabled={loading} type="submit" className="button flex items-center justify-center w-full mt-5 disabled:opacity-50">
<div className={`loading animate-spin mr-1 ${!loading ? 'hidden' : '' } `} /></div>
Login
</button>
</form>
</div>
</div>
);
}
export default Login;
store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
store/store.ts
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import reducer from './reducers'
export const store = configureStore({
reducer
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
store/reducers/reducers/index.ts
import {combineReducers} from 'redux'
import {authReducer} from './authReducer'
import {errorReducer} from './errorReducer'
const rootReducer = combineReducers({
auth: authReducer,
error: errorReducer
})
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
store/reducers /authreducer.ts
import { Auth } from "interfaces/Auth";
import {Action, ActionType} from 'store/types'
const initialState: Auth = {
user: {
id:'',
name: "",
email: ''
},
loading: false,
error:{
message: ''
}
};
export const authReducer = (state = initialState, action: Action) : Auth => {
switch (action.type) {
case ActionType.LOGIN_USER_START:
case ActionType.CHECK_USER_START:
return {
...state,
loading: true,
};
case ActionType.SET_USER:
return {
...state,
loading: false,
user: action.payload,
};
case ActionType.SET_USER_ERROR:
return {
...state,
loading: false,
error: action.payload,
};
case ActionType.PURGE_AUTH:
return state;
default:
return state;
}
}
,这是错误消息:
TypeError:无法读取未定义的属性
的属性'加载。13 | const导航= usenavigate() &gt; 14 | const Loading = useAppSelector(state =&gt; state.auth.loading)
发生的事情的属性“加载”?谢谢。
编辑
现在我修改了此商店/还原器/authreducer.ts
export const initialState: Auth = {
... 现在我正在导出它,以便在测试中进行测试:
...
import {initialState} from 'store/reducers/authReducer'
describe('Rendering', ()=>{
////I added auth
const initialStateLogin = {
auth: initialState
}
...
现在我的测试通过了,谢谢
I have a problem while setting up the environment for testing a React app that uses Redux and Typescript
Here is my test:
import React from 'react';
import { render, screen } from '@testing-library/react'
import Login from './Login'
import { Provider } from 'react-redux'
import configureStore from 'redux-mock-store'
import { MemoryRouter } from 'react-router-dom';
import {createMemoryHistory} from 'history'
describe('Rendering', ()=>{
const initialState = {
user: {
id:'',
name: "",
email: ''
},
loading: false,
error:{
message: ''
}
}
const mockStore = configureStore()
let store
test('renders initial screen', () => {
const history = createMemoryHistory()
store = mockStore(initialState)
render(
<Provider store={store}>
<MemoryRouter history={history}>
<Login />
</MemoryRouter>
</Provider>
)
const loginScreenMessage = screen.getByRole('heading', { name:/login/i } );
expect(loginScreenMessage).toBeInTheDocument();
})
})
This is the Login component:
import React, {useState, useEffect} from 'react'
import { useAppDispatch, useAppSelector } from 'store/hooks';
import {useNavigate} from 'react-router-dom';
import {loginUserAction} from 'store/actions/AuthAction'
import ValidationErrors from 'components/ValidationErrors'
function Login() {
const dispatch = useAppDispatch()
const navigate = useNavigate()
const loading = useAppSelector(state =>state.auth.loading)
const [form , setForm] = useState({
email : "",
password : ""
})
const error = useAppSelector(state =>state.auth.error)
useEffect(()=>{
dispatch(ErrorAction({message:''})
}, [])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setForm({
...form,
[e.target.name] : e.target.value
})
}
const submitLogin = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
try{
await dispatch(loginUserAction(form))
navigate('/home')
}catch{
//
}
}
return (
<div className="login" data-test="component-login">
<div className="w-full mx-auto sm:w-8/12 md:w-6/12 lg:w-4/12 bg-white pt-5 px-10">
<AiOutlineTwitter className="text-5xl text-twitter" />
<h1 className="text-4xl font-bold my-10 ">Login</h1>
{ error.message &&
<ValidationErrors errors={error} />
}
<form onSubmit={submitLogin}>
<div className="special-label-holder relative mt-5">
<input
type="text"
id="email"
name="email"
placeholder=" "
className="input-form"
value={form.email}
onChange={handleChange}
/>
<label
className="text-lg h-full left-2 text-gray-500 top-3 overflow-hidden pointer-events-none absolute transition-all duration-200 ease-in-out"
htmlFor="email"
>
Email, phone or username
</label>
</div>
<div className="special-label-holder relative mt-5">
<input
id="password"
type="password"
name="password"
placeholder=" "
className="input-form"
value={form.password}
onChange={handleChange}
/>
<label
className="text-lg h-full left-2 text-gray-500 top-3 overflow-hidden pointer-events-none absolute transition-all duration-200 ease-in-out"
htmlFor="password"
>
Password
</label>
</div>
<button disabled={loading} type="submit" className="button flex items-center justify-center w-full mt-5 disabled:opacity-50">
<div className={`loading animate-spin mr-1 ${!loading ? 'hidden' : '' } `} /></div>
Login
</button>
</form>
</div>
</div>
);
}
export default Login;
store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
store/store.ts
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import reducer from './reducers'
export const store = configureStore({
reducer
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
store/reducers/index.ts
import {combineReducers} from 'redux'
import {authReducer} from './authReducer'
import {errorReducer} from './errorReducer'
const rootReducer = combineReducers({
auth: authReducer,
error: errorReducer
})
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
store/reducers/authReducer.ts
import { Auth } from "interfaces/Auth";
import {Action, ActionType} from 'store/types'
const initialState: Auth = {
user: {
id:'',
name: "",
email: ''
},
loading: false,
error:{
message: ''
}
};
export const authReducer = (state = initialState, action: Action) : Auth => {
switch (action.type) {
case ActionType.LOGIN_USER_START:
case ActionType.CHECK_USER_START:
return {
...state,
loading: true,
};
case ActionType.SET_USER:
return {
...state,
loading: false,
user: action.payload,
};
case ActionType.SET_USER_ERROR:
return {
...state,
loading: false,
error: action.payload,
};
case ActionType.PURGE_AUTH:
return state;
default:
return state;
}
}
And this is the error message:
TypeError: Cannot read property 'loading' of undefined
13 | const navigate = useNavigate() > 14 | const loading = useAppSelector(state =>state.auth.loading)
What is happening? thanks.
Edit
Now I modified this store/reducers/authReducer.ts
export const initialState: Auth = {
...
Now I am exporting it, to get it in my test:
...
import {initialState} from 'store/reducers/authReducer'
describe('Rendering', ()=>{
////I added auth
const initialStateLogin = {
auth: initialState
}
...
Now my test is passing, thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
测试中的
初始状态
没有嵌套auth
指出您的组件似乎期望的。调整测试中的初始状态以反映应用程序使用的内容。更好的是,使用您的应用程序正在使用的相同初始状态。The
initialState
in your test has no nestedauth
state that your component seems to be expecting. Adapt the initialState in your test to reflect what the app uses. Better yet, use the same initialState your app is using.