A powerful HTTP client mocking library for Effect-based applications that captures real HTTP responses and replays them during tests. Perfect for creating reliable, deterministic tests without hitting external services.
- 🎭 Record & Replay: Capture real HTTP responses and replay them in tests
- 🚀 Effect Integration: Built on top of Effect and @effect/platform
- 💾 File-based Storage: Responses stored as files for easy versioning and inspection
- 🔄 Automatic Recording:
makeWithRecordautomatically captures missing responses - 🎯 Type-safe: Full TypeScript support with Effect's type system
- 🧪 Test Framework Agnostic: Works with any test runner (Bun, Jest, Vitest, etc.)
bun install mockhttpUse pre-recorded responses only. Fails if response not found:
import { MockHttpClient } from "mockhttp";
import { HttpClient } from "@effect/platform";
import { Effect, Layer } from "effect";
import { BunFileSystem } from "@effect/platform-bun";
// Create a mock client layer
const mockLayer = MockHttpClient.make("./.mock_responses").pipe(
Layer.provide(BunFileSystem.layer)
);
// Use in your tests
const program = Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
const response = yield* client.get("https://api.example.com/users");
return yield* response.json;
});
// Run with mock
const result = await program.pipe(
Effect.provide(mockLayer),
Effect.runPromise
);Automatically records responses for missing mocks:
import { FetchHttpClient } from "@effect/platform";
const mockWithRecordLayer = MockHttpClient.makeWithRecord("./.mock_responses").pipe(
Layer.provide(BunFileSystem.layer),
Layer.provide(FetchHttpClient.layer) // Real client for recording
);
// First run: makes real HTTP request and saves response
// Subsequent runs: uses saved response
const program = Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
const response = yield* client.get("https://httpbin.org/get");
return yield* response.json;
});MockHTTP stores HTTP responses as files in the specified directory. Each request is uniquely identified by:
- HTTP method
- Full URL (including query parameters)
The filename is a base64url encoding of "METHOD URL" with .http extension.
Responses are stored in raw HTTP format:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123
{"data": "response body"}
- Capturing: When a real response is received, it's serialized with status line, headers, and body
- Storage: The serialized response is saved to disk using Effect's FileSystem
- Replay: When the same request is made, the response is deserialized and returned
Creates a pure mock client that only uses pre-recorded responses.
dir: Directory to store/read responses (default:./.mock_responses)- Returns:
Layer<HttpClient.HttpClient, never, FileSystem.FileSystem>
Creates a recording mock client that captures missing responses.
dir: Directory to store/read responses (default:./.mock_responses)- Returns:
Layer<HttpClient.HttpClient, never, FileSystem.FileSystem> - Requires: An actual HttpClient layer (like FetchHttpClient) for recording
// Serialize a response to wire format
const serializeResponse: (
res: HttpClientResponse
) => Effect<Uint8Array, ResponseError, never>
// Deserialize from wire format
const deserializeResponse: (
bytes: Uint8Array,
req: HttpClientRequest
) => HttpClientResponseimport { describe, expect, it } from "bun:test";
import { Effect, Layer } from "effect";
import { BunFileSystem } from "@effect/platform-bun";
import { FetchHttpClient, HttpClient } from "@effect/platform";
import { MockHttpClient } from "mockhttp";
// Helper for Effect-based tests
export function itEffect<A, E>(
name: string,
eff: () => Effect<A, E, never>,
opts?: { timeoutMs?: number }
) {
return it(name, async () =>
await (opts?.timeoutMs
? eff().pipe(Effect.timeout(opts.timeoutMs))
: eff()
).pipe(
Effect.catchAllCause((cause) =>
Effect.sync(() => {
throw new Error(Cause.pretty(cause));
})
),
Effect.runPromise
)
);
}
describe("API Tests", () => {
const mockLayer = MockHttpClient.makeWithRecord("./tests/.mock_responses").pipe(
Layer.provide(BunFileSystem.layer),
Layer.provide(FetchHttpClient.layer)
);
itEffect("should fetch user data", () =>
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
const response = yield* client.get("https://api.example.com/user/123");
const user = yield* response.json;
expect(user.id).toBe(123);
}).pipe(Effect.provide(mockLayer))
);
});project/
├── src/
├── tests/
│ ├── .mock_responses/
│ │ ├── R0VUIGF_base64_encoded_request.http
│ │ └── UE9TVCBh_base64_encoded_request.http
│ └── api.test.ts
└── package.json
- Commit mock files: Add
.mock_responsesto version control - Deterministic tests: Tests always use the same responses
- Update mocks: Delete specific files to re-record responses
- Clean mocks:
rm -rf .mock_responsesto re-record all
const customMockLayer = MockHttpClient.make("./test-fixtures/http-mocks").pipe(
Layer.provide(BunFileSystem.layer)
);// Use different layers for different test suites
const integrationMocks = MockHttpClient.makeWithRecord("./mocks/integration");
const unitMocks = MockHttpClient.make("./mocks/unit");const program = Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
const response = yield* client.get("https://api.example.com/data");
return yield* response.json;
}).pipe(
Effect.catchTag("RequestError", (error) =>
Effect.succeed({ fallback: true })
)
);- Organize by Feature: Group mock files by feature or API endpoint
- Version Control: Commit mock files for reproducible tests
- Regular Updates: Periodically refresh mocks to catch API changes
- Review Changes: Review mock file diffs to spot API changes
- Environment Separation: Use different mock directories for different environments
If you get a file not found error, the mock doesn't exist yet:
- Switch to
makeWithRecordto capture it - Or manually create the mock file
If tests pass but production fails:
- Delete the mock file to force re-recording
- Compare old vs new mock to see what changed
Check the .mock_responses directory to see:
- What requests are being made
- What responses are being returned
- File naming patterns
Contributions are welcome! Please feel free to submit a Pull Request.
MIT