Search code examples
unit-testingjestjsserver-side-renderingnext.js13

How to unit test Nextjs 13 Page component ? I am rendering a Page component which is async. It makes an API call inside it to fetch data Server Side


I have written a Nextjs 13 page component which is an async function. It calls an API inside it and passes that response to the child component.

export default async function Home() {
  const participantsData: ParticipantInfo[] = await getParticipantsInfo();

  return (
    <div data-testid="home-page">
        <ParticipantsTable participantsInfo={participantsData} />
    </div>

  );
}

While unit testing it with jest,

const getRender = (): RenderResult => {
  return render(<Home />);
};

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve(participantsMockData),
  }),
) as jest.Mock;

describe("Home Page Tests", () => {
  it("renders Home Page Component", async () => {
    getRender();

    expect(screen.getByTestId("home-page")).toBeDefined();
  });
});

I am not able to render it because functional component is made async. I am getting the following error

console.error
    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.]
        at reportException (C:\git\axo-auth-ui\node_modules\jsdom\lib\jsdom\living\helpers\runtime-script-errors.js:66:24)
        at innerInvokeEventListeners (C:\git\axo-auth-ui\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:353:9)
        at invokeEventListeners (C:\git\axo-auth-ui\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:286:3)
        at HTMLUnknownElementImpl._dispatch (C:\git\axo-auth-ui\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:233:9)
        at HTMLUnknownElementImpl.dispatchEvent (C:\git\axo-auth-ui\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:104:17)
        at HTMLUnknownElement.dispatchEvent (C:\git\axo-auth-ui\node_modules\jsdom\lib\jsdom\living\generated\EventTarget.js:241:34)
        at Object.invokeGuardedCallbackDev (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:4213:16)
        at invokeGuardedCallback (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:4277:31)
        at beginWork$1 (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:27451:7)
        at performUnitOfWork (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:26560:12)
        at workLoopSync (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:26466:5)
        at renderRootSync (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:26434:7)
        at performConcurrentWorkOnRoot (C:\git\axo-auth-ui\node_modules\react-dom\cjs\react-dom.development.js:25738:74)

I tried to make the render async, but it did not work. I found the only solution is to not make the function async, and make an api call inside useEffect, but this makes the component client component, which I do not want. How do i render a functional component which is async in jest?


Solution

  • In case somebody stumbles upon this problem, I could figure out a solution by following this discussion: https://github.com/vercel/next.js/issues/42292

    // component
    // app/[locale]/page.tsx
    
    const Home = async ({ params }: { params: { locale: 'en' | 'de' } }) => {
      const { data } = await fetchProjects(locale) as { data: string[] };
      return (
         <HomePage {...{data}} />
      )
     }
    
    // I want to build paths on build time, use whatever you need in your case
    export const generateStaticParams = () => {
      return [
        { locale: 'en' },
        { locale: 'de' }
      ],
    }
    
    export default Home;
    
    
    // test
    describe("Home", () => {
      it("renders the home page with the correct props", async () => {
        const Resolved = await Home({ params: { locale: 'en' } });    
        const { getByText } = render(Resolved);
        expect(getByText("Project 1")).toBeInTheDocument();
      });
    });

    Just make sure that no ssr child is doing a fetch, so every fetch has to take place in page.tsx. Also a fetch should be mocked.