如何使用React,Redux和Typescript正确设置环境以进行测试?

发布于 2025-02-13 01:14:07 字数 8209 浏览 1 评论 0原文

我在设置用于使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

烏雲後面有陽光 2025-02-20 01:14:07

测试中的初始状态没有嵌套auth指出您的组件似乎期望的。调整测试中的初始状态以反映应用程序使用的内容。更好的是,使用您的应用程序正在使用的相同初始状态。

The initialState in your test has no nested auth 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文