Testing a component in Next.js with testing-library that relies on tRCP
Asked Answered
R

2

7

I was experimenting with tRCP and diligently followed the setup for my Next.js project described in the official docs over here: https://trpc.io/docs/nextjs

However I noticed that a simple component that relies on tRPC such as this

export const Sample = () => {
  const { data } = trpc.useQuery(['hello', { text: 'User' }]);
  if (data === undefined) {
    return <div>Loading...</div>;
  }
  return <div>{data.greeting}</div>;
};

cannot be properly tested since the following trivial test

describe('Sample', () => {
  it('should render successfully', () => {
    const { baseElement } = render(<Sample />);
    expect(baseElement).toBeTruthy();
  });
});

since there is no setup of provider such as the setup with the withTRCP HOC used for the application itself. As such the test fails claiming client (presumably the trcpClient, unlike the queryClient) is undefined.

I'd like to know how to setup the test correctly, in this case providing a correct client, as well as mocking the queries, since I don't have the respective server-side code running while invoking the tests.

Regenerator answered 16/7, 2022 at 1:10 Comment(1)
github.com/c-ehrlich/msw-trpc-example/tree/main - it's implemented using vitest but works with jest too. – Duel
R
0

Since you are getting undefined for the trpc client implementation, you can try spying on the query call.

import trpc from 'utils/trpc'; // This is the client implementation

describe('Sample', () => {
  it('should render successfully', () => {
    jest.spyOn(trpc, 'useQuery')
        .mockReturnValue({ greeting: "Greeting" });

    const { baseElement } = render(<Sample />);
    expect(baseElement).toBeTruthy();
  });
});

This is also possible with the mutations but you need to provide a mock implementation for the useMutation response for mutate property.

Rechabite answered 10/8, 2022 at 5:35 Comment(2)
Mocking the result of useQuery or useMutation respectively is highly inconvenient as the underlying react-query return type has two dozen other properties that require to be mocked as well making it VERY flaky for changes in the peer dependencies. – Regenerator
If you want to keep that API call within the component and make it an integration test, you do not really have any choice other than mocking. Here is the official doc for ReactQuery on mocking, you can for sure find something for SWR if you want to use it later if not you can try to detach the call from the component and pass values as props. Another option is using a vanilla client appRouter.createCaller(/*context*/).useQuery(['hello']) as a mock on the component call spy but then you would have to mock the context this tim. Good luck. – Rechabite
J
0

Based on my research, there is no documentation that helps you integrate React Testing Library and Next.js. However, I tried using the React way instead of the nonexistent Next.js way!

// src/utils/trpc.ts
export const trpc = createTRPCNext<AppRouter>(trpcConfig);
export const trpcReact = createTRPCReact<AppRouter>(trpcConfig);

So I have two instances with the same configuration, but one for React vanilla and one for Next.js app. That's kinda tricky! πŸ˜„

Then I use tRPCReact to create a provider/wrapper:

// src/vitest/decorator.tsx
import { httpBatchLink } from "@trpc/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { trpcReact } from "~/utils/trpc";

const url = `http://localhost:${process.env.PORT ?? 3000}/api/trpc`;

const queryClient = new QueryClient({
  defaultOptions: { queries: { staleTime: Infinity } },
});

const trpcClient = trpcReact.createClient({
  links: [httpBatchLink({ url })],
  transformer: superjson,
});

export const withNextTRPC = ({ children }: PropsWithChildren<{}>) => (
  <trpcReact.Provider client={trpcClient} queryClient={queryClient}>
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  </trpcReact.Provider>
);

And I use the regular trpc in my pages:

// src/pages/home.tsx
import { trpc } from "~/utils/trpc";

export default function Home() {
  const { data, isLoading, isFetching, error, isError } =
    trpc.hello.getHello.useQuery();

  return (
    <Box>
      <Head>
        <title>Use Stitches with Next.js and TRPC</title>
      </Head>
      <Container>
        {isLoading || isFetching ? (
          <Text>Loading...</Text>
        ) : (
          <Text>{data?.message}</Text>
        )}
        {isError && <Text>{error?.message}</Text>}
      </Container>
    </Box>
  );
}

Finally, to test a page component:

// src/__tests__/pages/home.spec.tsx
import { test } from "vitest";
import { render } from "@testing-library/react";
import Home from "~/pages";
import { withNextTRPC } from "vitest/decorator";

test("Home", async () => {
  render(<Home />, { wrapper: withNextTRPC });
});

Hope this helps you getting started with RTL + Next.js + tRPC.

Jochbed answered 9/3, 2023 at 18:35 Comment(3)
Where superjson comes from? – Enneagon
Hey, it's just a raw import from the dependecy import superjson from "superjson" – Jochbed
doesn't work when you use msw to intercept trpc calls. – Duel

© 2022 - 2024 β€” McMap. All rights reserved.