Mocking
Mocking is a technique used to replace real API calls with controlled, fake responses during testing. This is crucial for creating fast, reliable, and deterministic tests, as you don't have to rely on a real backend. With Hyper Fetch, you can leverage your existing request definitions to create mocks easily.
- Why mocking is essential for robust testing.
- How to create reusable mock handlers based on your Hyper Fetch requests.
- How to write tests using mocked API responses.
- How to centralize mocks with SDK
$configureand inject them into tests. - How to create per-test overrides for error states, empty states, and edge cases.
- How to mock Socket SDK listeners and emitters.
- How to use MSW (Mock Service Worker) to intercept requests.
Why Mock?
When you're testing your application, you want to ensure that your components and logic work correctly without being affected by external factors like network issues or backend changes. Mocking allows you to:
- Isolate your frontend code: Test your UI and business logic without needing a running backend.
- Control test data: Define the exact data your API calls should return, making it easy to test various scenarios (e.g., success, error, empty states).
- Increase test speed: Network requests are slow. Mocking them makes your tests run significantly faster.
- Avoid rate limiting: You won't hit real API rate limits during testing.
Build-in Mock Handlers
You can create a mock handler for your requests. This function will take a Hyper Fetch request and a mock response.
import { getUsers } from "src/api/users";
getUsers.setMock(() => ({ data: mockUsers }));
const response = await getUsers.send();
console.log(response); // { data: mockUsers }
Using Mocks in Tests
Now, let's see how you can use this setMock method in your tests.
import { screen } from "@testing-library/react";
import { getUsers } from "src/api/users";
import { Users } from "./Users";
it("should render a list of users", async () => {
// 1. Define the mock data
const mockUsers = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Jane Doe" },
];
// 2. Create and apply the mock handler
getUsers.setMock(() => ({ data: mockUsers }));
// 3. Render your component that's using HyperFetch to get the data
render(<Users />);
// 4. Assert that the component renders the mock data
expect(await screen.findByText("John Doe")).toBeInTheDocument();
expect(await screen.findByText("Jane Doe")).toBeInTheDocument();
});
By using Hyper Fetch you can create a powerful and maintainable testing setup where your mocks are always in sync with your application code.
Mocking with SDK Configuration
If you're using the SDK pattern (createSdk), you can centralize all your test mocks in a single $configure call.
The key insight is that $configure is immutable — it returns a brand new SDK instance with mocks baked in, so
your production code is never affected. You then inject this test SDK into your components during tests.
Creating a Test SDK
Create a shared test setup file that wraps your production SDK with mocks for every endpoint:
import { sdk } from "src/api/sdk";
import type { RequestInstance } from "@hyper-fetch/core";
const mockUsers = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Jane Doe" },
];
export const testSdk = sdk.$configure({
"users.$get": (request: RequestInstance) =>
request.setMock(() => ({ data: mockUsers })),
"users.$userId.$get": (request: RequestInstance) =>
request.setMock(({ request: req }) => ({
data: mockUsers.find((u) => u.id === Number(req.params?.userId)),
})),
"posts.$get": (request: RequestInstance) =>
request.setMock(() => ({ data: [] })),
});
Injecting the Test SDK
The test SDK is a drop-in replacement for the production SDK. Pass it into your components or hooks through props, context, or a dependency injection mechanism — whatever your app already uses:
import { useFetch } from "@hyper-fetch/react";
import type { SdkInstance } from "src/api/sdk";
// The component accepts the SDK as a prop, making it testable
export function Users({ sdk }: { sdk: SdkInstance }) {
const { data, loading, error } = useFetch(sdk.users.$get);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return <ul>{data?.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
import { screen, render } from "@testing-library/react";
import { testSdk } from "test/mocks";
import { Users } from "./Users";
it("should render a list of users", async () => {
render(<Users sdk={testSdk} />);
expect(await screen.findByText("John Doe")).toBeInTheDocument();
expect(await screen.findByText("Jane Doe")).toBeInTheDocument();
});
Per-Test Overrides
Since $configure is chainable, you can start from your base testSdk and override specific endpoints for individual
tests without affecting other tests:
import { testSdk } from "test/mocks";
import type { RequestInstance } from "@hyper-fetch/core";
it("should show empty state when no users", async () => {
const emptyUsersSdk = testSdk.$configure({
"users.$get": (request: RequestInstance) =>
request.setMock(() => ({ data: [] })),
});
render(<Users sdk={emptyUsersSdk} />);
expect(await screen.findByText("No users found")).toBeInTheDocument();
});
it("should show error state", async () => {
const errorSdk = testSdk.$configure({
"users.$get": (request: RequestInstance) =>
request.setMock(() => ({ data: null, error: { message: "Server error" }, status: 500 })),
});
render(<Users sdk={errorSdk} />);
expect(await screen.findByText("Error!")).toBeInTheDocument();
});
Mocking Response Mappers
You can also inject response mappers through $configure to test how your components handle transformed data:
const mappedSdk = sdk.$configure({
"users.$get": (request: RequestInstance) =>
request
.setMock(() => ({ data: rawApiResponse }))
.setResponseMapper((response) => response.data.users),
});
Socket SDK Mocking
The same pattern works for the Socket SDK. Create a test socket SDK with preconfigured listeners and emitters:
import { socketSdk } from "src/api/socket-sdk";
import type { SocketSdkConfigurationValue } from "@hyper-fetch/sockets";
const setTestOptions: SocketSdkConfigurationValue = (instance) =>
instance.setOptions({ test: true });
export const testSocketSdk = socketSdk.$configure({
"*": setTestOptions,
"chat.messages.$emitter": (instance) =>
instance.setPayloadMapper((payload) => ({
...payload,
testMode: true,
})),
});
- Centralized: All mocks live in one place, easy to find and update.
- Type-safe: The SDK schema validates that you're mocking real endpoints — typos are caught at compile time.
- Full control: Function-based values let you use
setMock,setResponseMapper, or any other Request setter. - Immutable:
$configurereturns a new SDK, so your production instance is never affected. - Composable: Chain
$configurecalls for per-test overrides without duplicating the entire mock setup. - No network layer: Mocks run inside the Request lifecycle — no need for MSW, interceptors, or fake servers.
Using MSW with Hyper Fetch
We recommend using MSW (Mock Service Worker) for mocking API requests. It's a powerful library that intercepts requests on the network level, meaning your application doesn't even know it's not talking to a real server.
The best part is that you can use your existing Hyper Fetch requests to configure MSW, which keeps your mocks in sync with your actual API definitions.
We are working on the MSW integration docs.
You've learned how to mock API requests in your Hyper Fetch application!
- You can explain the benefits of mocking for testing.
- You can create type-safe mock handlers that stay in sync with your API.
- You can centralize all test mocks using SDK
$configurewithsetMock. - You can inject a test SDK into components and create per-test overrides for specific scenarios.
- You can mock Socket SDK listeners and emitters with the same
$configurepattern. - You can set up MSW to work with your Hyper Fetch requests.
- You are able to write isolated and reliable tests for your components.
