From f6fa89f2b651e5e6dde74658c6474f03223262ba Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Jun 2025 19:07:29 -0400 Subject: [PATCH] feature: pass optional seed to faker --- docs/src/pages/guides/msw.md | 27 +++++++ packages/core/src/types.ts | 2 + packages/mock/src/msw/index.test.ts | 105 ++++++++++++++++++++++++++++ packages/mock/src/msw/index.ts | 8 ++- tests/configs/mock.config.ts | 8 ++- 5 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 packages/mock/src/msw/index.test.ts diff --git a/docs/src/pages/guides/msw.md b/docs/src/pages/guides/msw.md index 1466a488e..9dc76a44d 100644 --- a/docs/src/pages/guides/msw.md +++ b/docs/src/pages/guides/msw.md @@ -156,3 +156,30 @@ import { setupServer } from 'msw/node'; const server = setupServer(); server.use(...getPetsMock()); ``` + +--- + +### Deterministic Mock Data with `faker.seed` + +By default, faker generates random mock data on each run. You can repeat the same output with the `seed` option in your mock configuration. This will inject a `faker.seed(...)` call into your generated mock files, ensuring deterministic output. + +#### Configuration Example + +```typescript +import { defineConfig } from 'orval'; + +export default defineConfig({ + petstore: { + output: { + target: './src/petstore.ts', + mock: { + type: 'msw', + seed: 12345, // Set a deterministic seed + }, + }, + input: { + target: './petstore.yaml', + }, + }, +}); +``` diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c6c3dfd38..9d24e0aa0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -320,6 +320,8 @@ export type GlobalMockOptions = { baseUrl?: string; // This is used to set the locale of the faker library locale?: keyof typeof allLocales; + // This is used to seed the faker library for deterministic output + seed?: number | number[]; indexMockFiles?: boolean; }; diff --git a/packages/mock/src/msw/index.test.ts b/packages/mock/src/msw/index.test.ts new file mode 100644 index 000000000..16b76b0c1 --- /dev/null +++ b/packages/mock/src/msw/index.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect, vi } from 'vitest'; +import { generateMSWImports } from './index'; +import { OutputMockType } from '@orval/core'; + +// Mock the generateDependencyImports function +vi.mock('@orval/core', async () => { + const actual = + await vi.importActual('@orval/core'); + return { + ...actual, + generateDependencyImports: vi.fn( + () => + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";', + ), + }; +}); + +describe('generateMSWImports', () => { + const mockParams = { + implementation: 'mock-implementation', + imports: [], + specsName: { test: 'test-specs' }, + hasSchemaDir: false, + isAllowSyntheticDefaultImports: false, + }; + + describe('faker seed functionality', () => { + it('should return import block without seed when seed is undefined', () => { + const result = generateMSWImports({ + ...mockParams, + options: { type: OutputMockType.MSW }, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";', + ); + }); + + it('should add faker.seed call with numeric seed', () => { + const result = generateMSWImports({ + ...mockParams, + options: { type: OutputMockType.MSW, seed: 12345 }, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";\nfaker.seed(12345);\n\n', + ); + }); + + it('should add faker.seed call with array seed', () => { + const result = generateMSWImports({ + ...mockParams, + options: { type: OutputMockType.MSW, seed: [123, 456, 789] }, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";\nfaker.seed([123, 456, 789]);\n\n', + ); + }); + + it('should add faker.seed call with single element array', () => { + const result = generateMSWImports({ + ...mockParams, + options: { type: OutputMockType.MSW, seed: [42] }, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";\nfaker.seed([42]);\n\n', + ); + }); + + it('should add faker.seed call with zero seed', () => { + const result = generateMSWImports({ + ...mockParams, + options: { type: OutputMockType.MSW, seed: 0 }, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";\nfaker.seed(0);\n\n', + ); + }); + + it('should not add seed when array is empty', () => { + const result = generateMSWImports({ + ...mockParams, + options: { type: OutputMockType.MSW, seed: [] }, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";', + ); + }); + + it('should not add seed when options is undefined', () => { + const result = generateMSWImports({ + ...mockParams, + options: undefined, + }); + + expect(result).toBe( + 'import { http, HttpResponse } from "msw";\nimport { faker } from "@faker-js/faker";', + ); + }); + }); +}); diff --git a/packages/mock/src/msw/index.ts b/packages/mock/src/msw/index.ts index 945274f20..c3527ed19 100644 --- a/packages/mock/src/msw/index.ts +++ b/packages/mock/src/msw/index.ts @@ -53,13 +53,19 @@ export const generateMSWImports: GenerateMockImports = ({ isAllowSyntheticDefaultImports, options, }) => { - return generateDependencyImports( + const importBlock = generateDependencyImports( implementation, [...getMSWDependencies(options), ...imports], specsName, hasSchemaDir, isAllowSyntheticDefaultImports, ); + + const seed = options?.seed; + if (seed !== undefined && !(Array.isArray(seed) && seed.length === 0)) { + return `${importBlock}\nfaker.seed(${Array.isArray(seed) ? `[${seed.join(', ')}]` : seed});\n\n`; + } + return importBlock; }; const generateDefinition = ( diff --git a/tests/configs/mock.config.ts b/tests/configs/mock.config.ts index ce7f3d0b5..5e8343b96 100644 --- a/tests/configs/mock.config.ts +++ b/tests/configs/mock.config.ts @@ -89,7 +89,9 @@ export default defineConfig({ output: { target: '../generated/mock/petstore-tags-split/endpoints.ts', schemas: '../generated/mock/petstore-tags-split/model', - mock: true, + mock: { + seed: 1234, + }, mode: 'tags-split', client: 'axios', }, @@ -101,7 +103,9 @@ export default defineConfig({ output: { target: '../generated/mock/split/endpoints.ts', schemas: '../generated/mock/split/model', - mock: true, + mock: { + seed: [42, 101], + }, mode: 'split', client: 'axios', },