Testing an async server component with Jest in Next 13
Asked Answered
M

4

17

In NextJs 13+ using the experimental App folder, async server components can be written, as described by the documentation:

export default async function Page({ params: { username } }) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumsData = getArtistAlbums(username);

  // Wait for the promises to resolve
  const [artist, albums] = await Promise.all([artistData, albumsData]);

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  );
}

This is a very useful technique that I have implemented in many pages of my app. However, when testing with jest, I find that I am unable to write any tests that are able to render this default export:

it('should render without crashing', async () => {
  ...(setup mocks)
  const { container } = await waitFor(() => render(<Page params={{ username: 'Bob Dylan' }} />));
});

Any attempt to render the component or call it manually results in the following error:

Uncaught [Error: Objects are not valid as a React child (found: [object Promise])

Has anyone been able to correctly implement a Jest test using an async server component?

Mutazilite answered 14/3, 2023 at 5:7 Comment(4)
this may be a bug. I have posted an issue to the official github. Will update if there is a workaroundMutazilite
Are you using enzyme or something else?Clotho
@TomaszLenarcik not at the momentMutazilite
It seems like you're trying to use React Testing Library, since render and waitFor are in your test code. If not, feel free to remove the tags I added.Sarcastic
C
12

At the moment there's no official way to do it. You can use this workaround:

it('should render without crashing', async () => {
  // ...(setup mocks)
  render(await Page({params: {username: 'Bob Dylan' }}))
});

It's not perfect but it's the current solution. Check out this issue for more info.

Caracul answered 15/5, 2023 at 10:30 Comment(0)
S
2

You can test async components that don't use other RSC or app router features with React 18.3 (canary):

import { render, screen } from '@testing-library/react';
import { Suspense } from 'react';
import Page from './page';

it('should render without crashing', async () => {
  render(
    <Suspense>
      <Page params={{ username: 'Bob Dylan' }} />
    </Suspense>
  );
  await screen.findByRole('heading');
  expect(screen.getByRole('listitem')).toBeInTheDocument();
});

You may want to use a custom render function to simplify test setup if your suite heavily relies on async components.

If you need other RSC (i.e. server actions) or app router (i.e. layouts) features you can use hard coding, mocks, or an e2e test framework until we figure out native RSC rendering in #1209.

Sarcastic answered 26/5, 2023 at 1:55 Comment(0)
A
1

I waited for the async component to turn into a React Node. I mean:

render(
        await (async () => await AsyncComponent())()
    )

The discussion is still going under the Github Issue to this day, and NextJS guys suggest even using @//ts-expect-error, claiming that TypeScript team should correct it now.

Anta answered 5/7, 2023 at 11:22 Comment(0)
P
1

Find on github issue

async function resolvedComponent(Component: any, props?: any) {
const ComponentResolved = await Component(props);
return () => ComponentResolved;
}
describe("AboutUs component", () => {
it("Should display the content in French", async () => {
const AboutUsResolved = await resolvedComponent(AboutUs, {
  params: { lang: "fr" },
});

const BackToResolved = await resolvedComponent(Backto, { classStyles: "" });
render(<AboutUsResolved />);
render(<BackToResolved />);
// Wait for any pending promises to resolve
await screen.findByTestId("about_us_first_p");

const firstPTag = screen.getByTestId("about_us_first_p");

// Assert that the component displays the expected text content
expect(firstPTag.textContent).toContain("Example");
 });
 });
Pushing answered 5/10, 2023 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.