How to Use JSON Test Data in React and Next.js Projects
Building a React or Next.js app without a real backend yet? Or writing component tests that need realistic data? This guide covers every pattern for using JSON test data in React and Next.js — from simple local imports to full API mocking with MSW.
Pattern 1: Local JSON Import (Simplest)
The most straightforward approach — generate your data with Dummy JSON Generator, save the file, and import it directly.
// src/data/users.json ← your generated file goes here
// Then in your component:
import users from '@/data/users.json';
export default function UserList() {
return (
<ul>
{users.map(user => (
<li key={user.id}>
<img src={user.avatar} alt={user.fullName} />
<span>{user.fullName}</span>
<span>{user.email}</span>
</li>
))}
</ul>
);
}This works for static prototyping and Storybook stories but doesn't simulate real network behavior (loading states, errors, latency).
Pattern 2: Next.js API Route as Mock Endpoint
Create a Next.js API route that returns your JSON file. This simulates a real API call, including network latency if you want it.
// app/api/users/route.ts (Next.js App Router)
import { NextResponse } from 'next/server';
import users from '@/data/users.json';
export async function GET() {
// Optional: simulate network latency
await new Promise(r => setTimeout(r, 200));
return NextResponse.json(users);
}// pages/api/users.ts (Next.js Pages Router)
import type { NextApiRequest, NextApiResponse } from 'next';
import users from '@/data/users.json';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json(users);
}Your components now fetch from /api/users exactly like they would in production — the only difference is the data comes from your local JSON file.
Pattern 3: MSW — Mock Service Worker
MSW intercepts fetch and axios calls at the network level — in both the browser and Node.js (for Jest/Vitest). It's the most powerful approach for development and testing.
npm install msw --save-dev
npx msw init public/ --save// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
import users from '@/data/users.json';
import products from '@/data/products.json';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json(users);
}),
http.get('/api/users/:id', ({ params }) => {
const user = users.find(u => u.id === params.id);
if (!user) return new HttpResponse(null, { status: 404 });
return HttpResponse.json(user);
}),
http.get('/api/products', () => {
return HttpResponse.json(products);
}),
// Simulate a POST that returns the created resource
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: crypto.randomUUID(), ...body }, { status: 201 });
}),
];// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);// src/main.tsx or app/layout.tsx
if ("production" === 'development') {
const { worker } = await import('./mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
}Pattern 4: Vitest / Jest Component Tests with Fake Data
// src/mocks/server.ts (Node.js MSW server for tests)
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);// vitest.setup.ts
import { server } from './src/mocks/server';
import { beforeAll, afterAll, afterEach } from 'vitest';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());// UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import UserList from './UserList';
describe('UserList', () => {
it('renders users from the API', async () => {
render(<UserList />);
// Shows loading state first
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Then renders the fake data from our MSW handler
await waitFor(() => {
expect(screen.getAllByRole('listitem').length).toBeGreaterThan(0);
});
});
it('renders user emails', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('ayesha.rahman@example.com')).toBeInTheDocument();
});
});
});Pattern 5: Storybook Stories with Fake Data
// UserCard.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import UserCard from './UserCard';
import users from '@/data/users.json';
const meta: Meta<typeof UserCard> = {
component: UserCard,
};
export default meta;
type Story = StoryObj<typeof UserCard>;
export const Default: Story = {
args: { user: users[0] },
};
export const Inactive: Story = {
args: { user: users.find(u => u.status === 'inactive') },
};
export const LongName: Story = {
args: {
user: { ...users[0], fullName: 'Bartholomew Christophersen-Wellington' }
},
};Recommended File Structure
src/
├── data/ ← generated JSON files live here
│ ├── users.json
│ ├── products.json
│ └── orders.json
├── mocks/
│ ├── handlers.ts ← MSW route handlers
│ ├── browser.ts ← browser worker setup
│ └── server.ts ← Node.js server for tests
└── components/
└── UserList/
├── UserList.tsx
├── UserList.test.tsx
└── UserList.stories.tsxWhich Pattern to Use?
| Pattern | Best For | Simulates Network? |
|---|---|---|
| Local JSON import | Storybook, static prototypes | No |
| Next.js API route | Full-stack Next.js apps | Yes |
| MSW (browser) | Frontend dev without backend | Yes |
| MSW (Node.js) | Jest/Vitest component tests | Yes |
For most React/Next.js projects, the recommended setup is: Local JSON files (generated with Dummy JSON Generator) + MSW for both development and testing. It's the most realistic simulation of production behavior with the least boilerplate.