Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions docs/content/docs/guides/faker.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
title: Faker
description: Generate mock data factories with Faker.js from OpenAPI
---

Generate mock data factories powered by [Faker.js](https://fakerjs.dev/) from your OpenAPI specification. Faker output has no `msw` dependency, so it's useful for unit tests, Storybook stories, seed scripts, and any test setup that doesn't go through a network mock.

For Mock Service Worker request handlers, see the [MSW guide](/docs/guides/msw).

## Configuration

Add a `faker` generator entry to `output.mock.generators`:

```ts title="orval.config.ts"
import { defineConfig } from 'orval';

export default defineConfig({
petstore: {
output: {
mode: 'single',
target: './src/api/petstore.ts',
schemas: './src/api/model',
mock: {
generators: [{ type: 'faker' }],
},
},
input: {
target: './petstore.yaml',
},
},
});
```

You can also combine `msw` and `faker` to emit both files in the same run:

```ts
mock: {
generators: [{ type: 'msw' }, { type: 'faker' }],
}
```

The Faker output is written to `<filename>.faker.ts` and only depends on `@faker-js/faker`.

## Generated Output

### Response Factories

For each operation, Orval emits a `get<OperationId>ResponseMock` factory that returns a fully-populated response value:

Comment thread
coderabbitai[bot] marked this conversation as resolved.
```ts
import { faker } from '@faker-js/faker';

export const getShowPetByIdResponseMock = (
overrideResponse: Partial<Pet> = {},
): Pet => ({
id: faker.number.int({ min: undefined, max: undefined }),
name: faker.string.alpha(20),
tag: faker.string.alpha(20),
...overrideResponse,
});
```

Pass overrides for any subset of fields:

```ts
const pet = getShowPetByIdResponseMock({ name: 'Buddy' });
// => { id: 7272122785202176, name: "Buddy", tag: "..." }
```

## Options

Set faker-specific options on the generator entry:

```ts title="orval.config.ts"
mock: {
generators: [
{
type: 'faker',
useExamples: true,
generateEachHttpStatus: true,
locale: 'en_GB',
preferredContentType: 'application/json',
},
],
}
```

| Option | Type | Default | Description |
|---|---|---|---|
| `useExamples` | `boolean` | `false` | Seed mock values from OpenAPI `example`/`examples` fields when present. |
| `generateEachHttpStatus` | `boolean` | `false` | Emit a separate factory per HTTP status code defined in the spec (not just the success response). |
| `locale` | `keyof typeof allLocales` | — | Faker locale. Switches the import to `@faker-js/faker/locale/<x>` (e.g. `'en_GB'`, `'fr'`, `'ja'`). |
| `preferredContentType` | `string` | — | When an operation has multiple response content types, mock the one matching this MIME type. |

## Customizing Mock Values

Use `override.mock` to control how individual schemas, properties, and formats are mocked. These options apply to both `faker` and `msw` generators.

```ts title="orval.config.ts"
override: {
mock: {
properties: {
// Match by property name (string or regex)
email: () => faker.internet.email(),
'/.*Id$/': () => faker.string.uuid(),
},
format: {
// Match by OpenAPI `format` keyword
date: () => faker.date.past().toISOString(),
'date-time': () => faker.date.recent().toISOString(),
},
required: true, // Always populate optional fields
arrayMin: 3,
arrayMax: 5,
stringMin: 4,
stringMax: 20,
numberMin: 0,
numberMax: 100,
fractionDigits: 2,
},
}
```

You can also scope overrides per-operation or per-tag via `override.operations` and `override.tags`.

## Usage

### Unit Tests

```ts
import { describe, it, expect } from 'vitest';
import { getShowPetByIdResponseMock } from './api/petstore.faker';

describe('PetDetails', () => {
it('renders the pet name', () => {
const pet = getShowPetByIdResponseMock({ name: 'Buddy' });
render(<PetDetails pet={pet} />);
expect(screen.getByText('Buddy')).toBeInTheDocument();
});
});
```

### Storybook

```ts
import type { Meta, StoryObj } from '@storybook/react';
import { getShowPetByIdResponseMock } from '../api/petstore.faker';
import { PetDetails } from './PetDetails';

const meta: Meta<typeof PetDetails> = {
component: PetDetails,
};
export default meta;

export const Default: StoryObj<typeof PetDetails> = {
args: { pet: getShowPetByIdResponseMock() },
};

export const NamedPet: StoryObj<typeof PetDetails> = {
args: { pet: getShowPetByIdResponseMock({ name: 'Buddy' }) },
};
```

### Seed Scripts

```ts
import { writeFile } from 'node:fs/promises';
import { getListPetsResponseMock } from './api/petstore.faker';

const seed = Array.from({ length: 50 }, () => getListPetsResponseMock());
await writeFile('seed/pets.json', JSON.stringify(seed, null, 2));
```

## Deterministic Output

Faker's PRNG is seedable. Set a seed before invoking factories to get reproducible output, which is helpful for snapshot testing:

```ts
import { faker } from '@faker-js/faker';
import { getShowPetByIdResponseMock } from './api/petstore.faker';

beforeEach(() => {
faker.seed(42);
});

it('matches snapshot', () => {
expect(getShowPetByIdResponseMock()).toMatchSnapshot();
});
```

## Dynamic Imports

In `tags-split` mode, enable `mock.indexMockFiles` to emit an `index.faker.ts` aggregating all per-tag faker files:

```ts title="orval.config.ts"
export default defineConfig({
petstore: {
output: {
mode: 'tags-split',
mock: {
indexMockFiles: true,
generators: [{ type: 'faker' }],
},
},
},
});
```
1 change: 1 addition & 0 deletions docs/content/docs/guides/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"zod",
"client-with-zod",
"msw",
"faker",
"---Advanced---",
"enums",
"stream-ndjson",
Expand Down
63 changes: 9 additions & 54 deletions docs/content/docs/guides/msw.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ description: Generate Mock Service Worker handlers from OpenAPI

Generate [MSW (Mock Service Worker)](https://mswjs.io/) handlers from your OpenAPI specification to mock your API during development and testing.

For mock data factories without MSW request handlers, see the [Faker guide](/docs/guides/faker).

## Configuration

Set the `mock` option to `true`:
Set the `mock` option to `true` (emits both MSW handlers and Faker factories), or scope it to MSW only via the generator entry:

```ts title="orval.config.ts"
import { defineConfig } from 'orval';
Expand All @@ -18,7 +20,9 @@ export default defineConfig({
mode: 'single',
target: './src/api/petstore.ts',
schemas: './src/api/model',
mock: true,
mock: {
generators: [{ type: 'msw' }],
},
},
input: {
target: './petstore.yaml',
Expand All @@ -29,33 +33,9 @@ export default defineConfig({

## Generated Output

Orval generates three types of functions:
The MSW generator emits two types of functions per operation, plus an aggregator. Mock *data* (the `get<Op>ResponseMock` factories) is produced by the Faker generator — see the [Faker guide](/docs/guides/faker) for details on overriding values and formats.

### 1. Mock Data Generators

Functions that return mocked values using [Faker.js](https://fakerjs.dev/):

```ts
import { faker } from '@faker-js/faker';

export const getShowPetByIdResponseMock = (
overrideResponse: Partial<Pet> = {},
): Pet => ({
id: faker.number.int({ min: undefined, max: undefined }),
name: faker.string.alpha(20),
tag: faker.string.alpha(20),
...overrideResponse,
});
```

Override values as needed:

```ts
const pet = getShowPetByIdResponseMock({ name: 'Buddy' });
// => { id: 7272122785202176, name: "Buddy", tag: "..." }
```

### 2. Request Handlers
### 1. Request Handlers

Functions that bind mock data to [MSW](https://mswjs.io/) `http.*` handlers using the recommended [`HttpResponse`](https://mswjs.io/docs/api/http-response) class:

Expand Down Expand Up @@ -115,7 +95,7 @@ export default defineConfig({

`preferredContentType` accepts common MIME literals and any custom string (via a loose `(string & {})` fallback), so vendor-specific types are supported too.

### 3. Aggregated Handlers
### 2. Aggregated Handlers

Functions that combine all handlers for easy setup:

Expand Down Expand Up @@ -269,31 +249,6 @@ export { server };

If both `msw` and `faker` generators are configured with `indexMockFiles: true`, you also get an `index.faker.ts` alongside `index.msw.ts`.

## Faker-only Output

If you only need mock data factories without MSW request handlers, use a `faker` generator entry. It emits the same `get<OperationId>ResponseMock` factory functions, but with no `msw` import or HTTP handler code:

```ts title="orval.config.ts"
export default defineConfig({
petstore: {
output: {
mock: {
generators: [{ type: 'faker' }],
},
},
},
});
```

The output is written to `<filename>.faker.ts` and only depends on `@faker-js/faker`. Useful for:

- Unit tests with custom assertion libraries
- Storybook stories
- Seed scripts
- Any test setup that doesn't use MSW

You can combine both generators (e.g. `generators: [{ type: 'msw' }, { type: 'faker' }]`) to emit both files in the same run.

## MSW Best Practices

The generated code follows [MSW best practices](https://mswjs.io/docs/best-practices):
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ export interface MswMockOptions extends CommonMockOptions {

export interface FakerMockOptions extends CommonMockOptions {
type: typeof OutputMockType.FAKER;
// Emit a consolidated mock factory file for every entry under
// `components/schemas` (one `get<SchemaName>Mock` per schema). Defaults to
// `false` — schema factories are opt-in to preserve existing output.
schemas?: boolean;
// Emit per-operation response mock factories (the historical behavior).
// Defaults to `true`. Set to `false` together with `schemas: true` to get
// only the consolidated schema factories.
operationResponses?: boolean;
}

export type GlobalMockOptions = MswMockOptions | FakerMockOptions;
Expand Down Expand Up @@ -1134,6 +1142,10 @@ export interface GeneratorImport {
readonly syntheticDefaultImport?: boolean;
readonly namespaceImport?: boolean;
readonly importPath?: string;
// True when this import points at a generated schema-level faker factory
// (e.g. `getPetMock`). The mock-file writer routes it to
// `<schemas-dir>/index.faker` instead of `<schemas-dir>/<schemaName>`.
readonly schemaFactory?: boolean;
}

export interface GeneratorDependency {
Expand Down
23 changes: 22 additions & 1 deletion packages/core/src/writers/generate-imports-for-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ export function generateImportsForBuilder(
const isZodSchemaOutput =
isObject(output.schemas) && output.schemas.type === 'zod';

// Schema-factory imports (`getPetMock` and friends) always resolve to the
// consolidated `<schemas-dir>/index.faker` file emitted by the faker
// schemas option. They bypass the per-schema convention naming below.
const schemaFactoryImports = imports.filter((i) => i.schemaFactory);
const schemaFactoryDeps: GeneratorDependency[] =
schemaFactoryImports.length > 0
? [
{
exports: uniqueBy(
schemaFactoryImports,
(entry) => `${entry.name}|${entry.alias ?? ''}`,
),
dependency: upath.joinSafe(relativeSchemasPath, 'index.faker'),
},
]
: [];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// The rest of the schema-import bucket is for types emitted alongside
// each schema (`Pet`, `PetWithTag`, ...). They're routed below.
imports = imports.filter((i) => !i.schemaFactory);

let schemaImports: GeneratorDependency[];
if (output.indexFiles) {
schemaImports = isZodSchemaOutput
Expand Down Expand Up @@ -80,5 +101,5 @@ export function generateImportsForBuilder(
};
});

return [...schemaImports, ...otherImports];
return [...schemaImports, ...schemaFactoryDeps, ...otherImports];
}
1 change: 1 addition & 0 deletions packages/core/src/writers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './file';
export * from './schemas';
export * from './single-mode';
export * from './split-mode';
Expand Down
Loading
Loading