使用 Jest 将自定义挂钩返回值模拟为module返回错误值

IT技术 reactjs unit-testing jestjs
2022-07-07 02:07:02

在对 React 组件进行单元测试时,我需要模拟我的自定义钩子。我已经阅读了一些 stackoverflow 的答案,但没有成功地正确实现它。

我不能在不模拟的情况下使用 useAuth,因为它取决于服务器请求,而我目前只编写单元测试。

//useAuth.js - custom hook

import React, { createContext, useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';

const authContext = createContext();

function useProvideAuth() {
    const [accessToken, setAccessToken] = useState('');
    const [isAuthenticated, setAuthenticated] = useState(
        accessToken ? true : false
    );

    useEffect(() => {
        refreshToken();
    }, []);

    const login = async (loginCredentials) => {
        const accessToken = await sendLoginRequest(loginCredentials);
        if (accessToken) {
            setAccessToken(accessToken);
            setAuthenticated(true);
        }
    };

    const logout = async () => {
        setAccessToken(null);
        setAuthenticated(false);
        await sendLogoutRequest();
    };

    const refreshToken = async () => {
        const accessToken = await sendRefreshRequest();
        if (accessToken) {
            setAccessToken(accessToken);
            setAuthenticated(true);
        } else setAuthenticated(false);
        setTimeout(async () => {
            refreshToken();
        }, 15 * 60000 - 1000);
    };

    return {
        isAuthenticated,
        accessToken,
        login,
        logout
    };
}

export function AuthProvider({ children }) {
    const auth = useProvideAuth();

    return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}

AuthProvider.propTypes = {
    children: PropTypes.any
};

const useAuth = () => {
    return useContext(authContext);
};

export default useAuth;

我写的测试

import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import { NavBar } from '../App';

jest.resetAllMocks();
jest.mock('../auth/useAuth', () => {
    const originalModule = jest.requireActual('../auth/useAuth');
    return {
        __esModule: true,
        ...originalModule,
        default: () => ({
            accessToken: 'token',
            isAuthenticated: true,
            login: jest.fn,
            logout: jest.fn
        })
    };
});

describe('NavBar when isAuthenticated', () => {
    it('LogOut button is visible when isAuthenticated', () => {
        render(<NavBar />);
        expect(screen.getByText(/log out/i)).toBeVisible();
    });
});

我正在编写测试的功能:

//App.js
import React from 'react';
import cn from 'classnames';
import useAuth, { AuthProvider } from './auth/useAuth';
import './App.css';
import '../node_modules/bootstrap/dist/css/bootstrap.css';

function App() {
    return (
        <AuthProvider>
            <Router>
                    <NavBar />
            </Router>
        </AuthProvider>
    );
}

const NavBarSignUpButton = () => (
    <button className='button info'>
        Sign up
    </button>
);

const NavBarLogoutButton = () => {
    const auth = useAuth();
    const handleLogOut = () => {
        auth.logout();
    };

    return (
        <button className='button info' onClick={handleLogOut}>
            Log out
        </button>
    );
};

export const NavBar = () => {
    const isAuthenticated = useAuth().isAuthenticated;
    const loginButtonClassName = cn({
        btn: true,
        invisible: isAuthenticated
    });

    return (
        <nav className='navbar navbar-expand-lg navbar-light'>
            <div className='container'>
                <div className='d-flex justify-content-end'>
                    <div className='navbar-nav'>
                        <button className={loginButtonClassName}>
                            Log In
                        </button>
                        {isAuthenticated ? <NavBarLogoutButton /> : <NavBarSignUpButton />}
                    </div>
                </div>
            </div>
        </nav>
    );
};

上面的测试代码不会抛出任何错误。但是,测试失败,因为 useAuth().isAuthenticated 始终为假(但我在模拟它以返回真)。无论我测试 App 还是仅测试 NavBar 都不会改变

我究竟做错了什么?

1个回答

我做了一个超级缩小的例子,应该展示嘲弄的作品。它只是具有钩子本身和一个返回YESNO基于钩子的组件。考试

使用Auth.js

import {createContext, useContext} from 'react'

const authContext = createContext()

const useAuth = () => {
    return useContext(authContext)
}

export default useAuth

组件.js

import useAuth from './useAuth'

export const Component = () => {
    const isAuthenticated = useAuth().isAuthenticated

    return isAuthenticated ? 'YES' : 'NO'
}

组件.test.js

import React from 'react'
import {render, screen} from '@testing-library/react'
import {Component} from './component'

jest.mock('./useAuth', () => {
    const originalModule = jest.requireActual('./useAuth')
    return {
        __esModule: true,
        ...originalModule,
        default: () => ({
            accessToken: 'token',
            isAuthenticated: true,
            login: jest.fn,
            logout: jest.fn,
        }),
    }
})

describe('When isAuthenticated', () => {
    it('Component renders YES', () => {
        render(<Component />)
        screen.getByText(/YES/i)
    })
})

在这种情况下,组件实际上会渲染YES并且测试通过。这让我觉得还涉及其他事情。当我将模拟更改为 时false,测试失败,因为它呈现NO.