How to Mock tRPC Communication on Storybook with MSW
Introduction
tRPC is a framework that allows you to easily build type-safe APIs. During development, if you want to proceed with front-end development on Storybook without waiting for the backend implementation, you can mock the API using Mock Service Worker (MSW). In this article, we will explain how to mock tRPC communication with MSW using maloguertin/msw-trpc. As a practical example, the sample code is shared on the GitHub repository silverbirder/trpc-msw-storybook-nextjs.
Technology Stack
First, let's introduce the libraries we will use.
The following dependencies are listed in package.json
. (Only a part is extracted)
{
"dependencies": {
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.45.1",
"@trpc/next": "^10.45.1",
"@trpc/react-query": "^10.45.1",
"@trpc/server": "^10.45.1",
"next": "^14.1.0",
"react": "18.2.0"
},
"devDependencies": {
"@storybook/nextjs": "^7.6.17",
"@storybook/react": "^7.6.17",
"msw": "^2.2.2",
"msw-storybook-addon": "^2.0.0--canary.122.b3ed3b1.0",
"msw-trpc": "^2.0.0-beta.0",
"storybook": "^7.6.17"
}
}
Preparation
To create a project template, execute the following command.
npm create t3-app@latest
npx storybook@latest init
npm i msw
npx msw init ./public --save
This will set up the basic integration of tRPC and MSW.
Sample Component
Next, the component used as a sample is as follows. It is a component generated by t3-app, with some modifications.
// ~/app/_components/create-post.tsx
"use client";
import { useState } from "react";
import { api } from "~/trpc/react";
export function CreatePost() {
const [name, setName] = useState("");
const [postName, setPostName] = useState("");
const createPost = api.post.create.useMutation({
onSuccess: ({name}) => {
setName("");
setPostName(name);
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
createPost.mutate({ name });
}}
className="flex flex-col gap-2"
>
<input
type="text"
placeholder="Title"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full rounded-full px-4 py-2 text-black"
/>
<button
type="submit"
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
disabled={createPost.isLoading}
>
{createPost.isLoading ? "Submitting..." : "Submit"}
</button>
<div>{postName}</div>
</form>
);
}
We are using api.post.create.useMutation
to send data via tRPC.
Integration of MSW and tRPC
To integrate MSW and tRPC, first install the maloguertin/msw-trpc package.
npm i msw-trpc --save-dev
Then, place the MSW and tRPC integration code in ~/trpc/msw.tsx
and write it as follows.
// ~/trpc/msw.tsx
"use client";
import { useState } from "react";
import { createTRPCMsw } from "msw-trpc";
import { httpLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { getUrl, transformer } from "./shared";
import type { AppRouter } from "~/server/api/root";
export const trpcMsw = createTRPCMsw<AppRouter>({
baseUrl: getUrl(),
transformer: { input: transformer, output: transformer },
});
export const api = createTRPCReact<AppRouter>();
export const TRPCReactProvider = (props: { children: React.ReactNode }) => {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
api.createClient({
transformer,
links: [
httpLink({
url: getUrl(),
headers() {
return {
"content-type": "application/json",
};
},
}),
],
}),
);
return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
);
};
trpcMsw
is used in the MSW handler.
For more details, please check createTRPCMsw
in maloguertin/msw-trpc.
I was in trouble because I didn't understand the arguments of createTRPCMsw at first.
TRPCReactProvider
is the MSW version of the tRPC provider.
Integration of MSW and Storybook
To run MSW in Storybook, use mswjs/msw-storybook-addon. To install, execute the following command.
npm i msw-storybook-addon --save-dev
※ For various reasons, we specify msw-storybook-addon@2.0.0--canary.122.b3ed3b1.0
.
To set up the display in Storybook, configure .storybook/preview.tsx
as follows.
// /.storybook/preview.tsx
import React from "react";
import type { Preview } from "@storybook/react";
import { initialize, mswLoader } from "msw-storybook-addon";
import { TRPCReactProvider } from "../src/trpc/msw";
initialize();
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
loaders: [mswLoader],
decorators: [
(Story) => (
<TRPCReactProvider>
<Story />
</TRPCReactProvider>
),
],
};
export default preview;
Wrap decorators with TRPCReactProvider
.
This allows you to mock tRPC communication in Storybook with MSW.
Storybook's stories file
Finally, the Storybook's stories file is defined as follows.
// ~/app/_components/create-post.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { CreatePost } from './create-post';
import { trpcMsw } from '~/trpc/msw';
const meta = {
title: 'create-post',
component: CreatePost,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
backgroundColor: { control: 'color' },
},
} satisfies Meta<typeof CreatePost>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Main: Story = {
parameters: {
msw: {
handlers: [
trpcMsw.post.create.mutation(({name}) => {
const post = { id: 1, name: name };
return post;
})
],
},
}
};
In parameters.switch.handlers
, we are mocking the tRPC's mutation with MSW.
Now, when you run npm run storybook
, you can check the UI with the mocked tRPC.
In conclusion
I hope this article has been helpful to you.
Share
Related tags
- Trying Out container-structure-test on a Docker Image
- Integration Testing for Next.js and DB using Testcontainers
- Trying Million Lint
- Trying the Playwright Component Test
- I want to do Document Testing with Typescript
- E2E Testing with Cucumber and Screenplay Design
- Comprehensive Testing Pattern Guide for Web Frontend
- Mockable unit testing methodology completed only with BigQuery
- Trying ArchUnit with Typescript
- Investigated and Summarized Testing Perspectives for Web Apps (25 Selections)
- I want to test with Google Apps Script too! (Clasp + Typescript + Jest)
- Things to Keep in Mind When Writing Unit Tests
- Continuously Monitor Visual Regression Tests with CircleCI + BackstopJS (Puppeteer)