如何用React Testing Library测试含Context/Provider的EventCard组件
我开发了一个基于React Context的Web应用,需要测试EventCard组件:当条目属于当前用户时显示拒绝按钮。我通过AuthProvider对接后端API处理登录登出,成功登录后将用户对象存入Cookie维持会话。但测试模拟登录状态时,EventCard内调用useAuth的getUserData()返回的personId始终为undefined,而测试组件内通过jest.spyOn模拟的getUserData却能正常返回123。请问如何使用React Testing Library测试依赖React Context的组件?
测试代码
import { render, screen } from "@testing-library/react"; import { EventCard } from "../EventCard"; import { stdEventRequest } from "../../helpers/standard-request-for-tests"; import { AuthProvider, useAuth, withAuth } from "../../context/auth/AuthProvider"; import { FC } from "react"; import { CookieUniversalProvider } from "@shopify/react-cookie"; /** * A summary of an event should look something like this: * * ============================================================================ * John Doe (10:30) * Guitar [x] (a decline button) * Please let your leader know why you can't make it [] [if volunteer clicks Decline] * ------------------------------------------------- * ============================================================================ */ it('should show a decline button if current user', () => { const Component: FC = withAuth(() => { const auth = useAuth(); jest.spyOn(auth, 'getUserData').mockReturnValue({ personId: 123, ... }); return ( <CookieUniversalProvider> <AuthProvider> <EventCard request={{ personId: 123, personName: 'John Doe', positionId: 10, positionName: 'Guitar', }} /> personId: { auth.getUserData().personId } </AuthProvider> </CookieUniversalProvider> ) }); render(<Component />); const positionElement = screen.getByText(/Guitar/i); const button = screen.getByRole('button'); expect(positionElement).toBeInTheDocument(); expect(button).toBeInTheDocument(); });
我添加了personId: { auth.getUserData().personId }验证spyOn,它能正常显示123。
EventCard组件代码
export const EventCard = ({request}: EventCardProps) => { const { getUserData } = useAuth(); return ( <View> personId: {getUserData().personId} {request.personId === getUserData().personId ? (<> <Text fontWeight={600}> {request.positionName} <AcceptDeclineButton category="decline" /> </Text> </>) : (<> <Text fontSize='small'> {statusToName(request)} </Text> <Text fontSize='small'> {request.positionName} </Text> </>)} </View> ); }
我在组件中添加了personId: {getUserData().personId},结果返回undefined。
核心问题分析
你在测试中mock的getUserData只作用于当前组件的useAuth实例,而EventCard是在AuthProvider内部重新获取的独立实例,两者毫无关联,所以EventCard里的getUserData仍然是原始实现,返回undefined。
解决方案
方案一:直接Mock useAuth钩子
使用jest.mock全局mockuseAuth钩子,让所有组件调用时都返回预设的用户数据:
import { render, screen } from "@testing-library/react"; import { EventCard } from "../EventCard"; import { AuthProvider, useAuth } from "../../context/auth/AuthProvider"; import { CookieUniversalProvider } from "@shopify/react-cookie"; // Mock useAuth钩子,保留其他原始导出 jest.mock("../../context/auth/AuthProvider", () => ({ ...jest.requireActual("../../context/auth/AuthProvider"), useAuth: () => ({ getUserData: () => ({ personId: 123 }) // 若需要其他Auth方法,可在此添加mock实现 }) })); it('should show a decline button if current user', () => { render( <CookieUniversalProvider> <AuthProvider> <EventCard request={{ personId: 123, personName: 'John Doe', positionId: 10, positionName: 'Guitar', }} /> </AuthProvider> </CookieUniversalProvider> ); const positionElement = screen.getByText(/Guitar/i); const button = screen.getByRole('button'); expect(positionElement).toBeInTheDocument(); expect(button).toBeInTheDocument(); });
方案二:创建测试专用AuthProvider
如果需要灵活控制不同测试场景的用户状态,可以封装一个测试用的Provider,预先注入模拟数据:
import { render, screen } from "@testing-library/react"; import { EventCard } from "../EventCard"; import { AuthProvider } from "../../context/auth/AuthProvider"; import { CookieUniversalProvider } from "@shopify/react-cookie"; // 测试用AuthProvider,接收自定义userData const TestAuthProvider = ({ children, userData }) => { const mockAuthValue = { getUserData: () => userData, // 按需添加其他需要mock的Auth方法 }; // 假设AuthProvider支持传入value覆盖默认Context值 return ( <AuthProvider value={mockAuthValue}> {children} </AuthProvider> ); }; it('should show a decline button if current user', () => { render( <CookieUniversalProvider> <TestAuthProvider userData={{ personId: 123 }}> <EventCard request={{ personId: 123, personName: 'John Doe', positionId: 10, positionName: 'Guitar', }} /> </TestAuthProvider> </CookieUniversalProvider> ); const positionElement = screen.getByText(/Guitar/i); const button = screen.getByRole('button'); expect(positionElement).toBeInTheDocument(); expect(button).toBeInTheDocument(); });
为什么原代码无效?
原测试中,你在withAuth包裹的组件里获取的auth实例,和EventCard内部通过useAuth获取的实例是两个完全独立的对象。EventCard位于AuthProvider的子组件树中,它会从AuthProvider的Context中获取自己的实例,外层组件的spyOn操作无法影响到它。
内容的提问来源于stack exchange,提问作者cabcookie

