You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为含异步服务端组件的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 22:55:33