如何为含异步服务端组件的Next.js布局编写Jest单元测试
解决Jest无法处理Next.js异步服务端组件的测试问题
问题背景
我写了一个Next.js的RootLayout布局组件,里面引入了异步服务端组件Header:
RootLayout代码:
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <NextAuthProvider> <Header /> {children} </NextAuthProvider> </body> </html> ) }
Header是异步服务端组件,代码如下:
const Header = async () => { const session = await getServerSession(authOptions) return ... }
用Jest和Testing Library写单元测试时,出现了报错:
Error: Uncaught [Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.]
我试过两种测试写法,但只有把Header的async修饰符去掉才能正常运行:
第一种常规渲染写法:
describe("RootLayout", () => { it("should render correct", async () => { const { getByText } = render( <RootLayout> <div>Test</div> </RootLayout> ) expect(getByText("Test")).toBeInTheDocument() }) })
第二种直接调用组件获取JSX的写法:
describe("RootLayout", () => { it("should render correct", async () => { const jsx = await RootLayout({ children: <div>Test</div> }) const { getByText } = render(jsx) expect(getByText("Test")).toBeInTheDocument() }) })
解决方案
方法1:Mock异步Header组件(推荐)
测试RootLayout的核心逻辑时,不需要依赖Header的真实服务端逻辑,直接用Jest把Header Mock成一个同步静态组件即可:
import RootLayout from './RootLayout' import Header from './Header' // Mock异步Header组件为同步组件 jest.mock('./Header', () => { return { __esModule: true, default: () => <div>Mocked Header</div> } }) describe("RootLayout", () => { it("should render correct", () => { const { getByText } = render( <RootLayout> <div>Test</div> </RootLayout> ) expect(getByText("Test")).toBeInTheDocument() // 可选:验证Mock的Header是否被渲染 expect(getByText("Mocked Header")).toBeInTheDocument() }) })
这样Jest会用我们定义的同步组件替换原有的异步Header,彻底避免渲染Promise的问题。
方法2:用Suspense包裹并Mock异步依赖
如果需要保留Header的部分逻辑,可在测试中用Suspense包裹RootLayout,同时MockgetServerSession方法让异步逻辑正常resolve:
import { Suspense } from 'react' import { getServerSession } from 'next-auth/next' import RootLayout from './RootLayout' // Mock getServerSession返回模拟的session数据 jest.mock('next-auth/next', () => ({ getServerSession: jest.fn().mockResolvedValue({ user: { name: '测试用户' } }) })) describe("RootLayout", () => { it("should render correct", async () => { const { getByText, findByText } = render( <Suspense fallback={<div>加载中...</div>}> <RootLayout> <div>Test</div> </RootLayout> </Suspense> ) // 先断言加载态存在 expect(getByText("加载中...")).toBeInTheDocument() // 等待异步组件渲染完成后断言内容 expect(await findByText("Test")).toBeInTheDocument() }) })
方法3:直接解析异步组件后渲染
如果只想验证RootLayout的结构,可先手动解析异步Header,再渲染RootLayout:
import RootLayout from './RootLayout' import Header from './Header' describe("RootLayout", () => { it("should render correct", async () => { // Mock Header的异步返回值 jest.spyOn(Header, 'default').mockResolvedValue(<div>Header</div>) // 直接调用RootLayout组件函数,等待内部异步组件解析完成 const jsx = await RootLayout({ children: <div>Test</div> }) const { getByText } = render(jsx) expect(getByText("Test")).toBeInTheDocument() }) })
这种方法相对繁琐,更适合需要深入验证组件内部结构的场景。
内容的提问来源于stack exchange,提问作者dungreact
相关产品推荐
相关产品推荐

