Skip to content

Commit

Permalink
feat: generate mocks for all responses (#1284)
Browse files Browse the repository at this point in the history
* feat: generate mocks for all responses

* refactor: only generate all http mocks if generateEachHttpStatus is defined and remove example

* refactor: use isObject to check options
  • Loading branch information
huwshimi authored Apr 9, 2024
1 parent a9ef135 commit 5cab47d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 23 deletions.
12 changes: 12 additions & 0 deletions docs/src/pages/reference/configuration/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ Type: `Boolean`.

Gives you the possibility to use the `example`/`examples` fields from your OpenAPI specification as mock values.

#### generateEachHttpStatus

Type: `Boolean`.

Gives you the possibility to generate mocks for all the HTTP statuses in the `responses` fields in your OpenAPI specification.

#### baseUrl

Type: `String`.
Expand Down Expand Up @@ -1024,6 +1030,12 @@ Type: `boolean`.
Gives you the possibility to have functions that are passed to `delay` to be
executed at runtime rather than when the mocks are generated.

#### generateEachHttpStatus

Type: `Boolean`.

Gives you the possibility to generate mocks for all the HTTP statuses in the `responses` fields in your OpenAPI specification.

##### arrayMin

Type: `Number`.
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ export type GlobalMockOptions = {
type: OutputMockType;
// This is the option to use the examples from the openapi specification where possible to generate mock data
useExamples?: boolean;
// This is used to generate mocks for all http responses defined in the OpenAPI specification
generateEachHttpStatus?: boolean;
// This is used to set the delay to your own custom value
delay?: number | (() => number);
// This is used to execute functions that are passed to the 'delay' argument
Expand Down
103 changes: 91 additions & 12 deletions packages/mock/src/msw/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import {
generateDependencyImports,
GenerateMockImports,
GeneratorDependency,
GeneratorImport,
GeneratorOptions,
GeneratorVerbOptions,
isFunction,
isObject,
pascal,
ResReqTypesValue,
} from '@orval/core';
import { getRouteMSW, overrideVarName } from '../faker/getters';
import { getMockDefinition, getMockOptionsDataOverride } from './mocks';
Expand Down Expand Up @@ -43,23 +46,30 @@ export const generateMSWImports: GenerateMockImports = ({
);
};

export const generateMSW = (
const generateDefinition = (
name: string,
route: string,
getResponseMockFunctionNameBase: string,
handlerNameBase: string,
{ operationId, response, verb, tags }: GeneratorVerbOptions,
{ pathRoute, override, context, mock }: GeneratorOptions,
{ override, context, mock }: GeneratorOptions,
returnType: string,
status: string,
responseImports: GeneratorImport[],
responses: ResReqTypesValue[],
contentTypes: string[],
) => {
const { definitions, definition, imports } = getMockDefinition({
operationId,
tags,
response,
returnType,
responses,
imports: responseImports,
override,
context,
mockOptions: !isFunction(mock) ? mock : undefined,
});

const route = getRouteMSW(
pathRoute,
override?.mock?.baseUrl ?? (!isFunction(mock) ? mock?.baseUrl : undefined),
);
const mockData = getMockOptionsDataOverride(operationId, override);

let value = '';
Expand All @@ -73,12 +83,11 @@ export const generateMSW = (
}

const isResponseOverridable = value.includes(overrideVarName);
const isTextPlain = response.contentTypes.includes('text/plain');
const isTextPlain = contentTypes.includes('text/plain');
const isReturnHttpResponse = value && value !== 'undefined';

const returnType = response.definition.success;
const getResponseMockFunctionName = `get${pascal(operationId)}ResponseMock`;
const handlerName = `get${pascal(operationId)}MockHandler`;
const getResponseMockFunctionName = `${getResponseMockFunctionNameBase}${pascal(name)}`;
const handlerName = `${handlerNameBase}${pascal(name)}`;

const mockImplementation = isReturnHttpResponse
? `export const ${getResponseMockFunctionName} = (${isResponseOverridable ? `overrideResponse: any = {}` : ''})${mockData ? '' : `: ${returnType}`} => (${value})\n\n`
Expand All @@ -97,7 +106,7 @@ export const ${handlerName} = (${isReturnHttpResponse && !isTextPlain ? `overrid
: null
},
{
status: 200,
status: ${status === 'default' ? 200 : status.replace(/XX$/, '00')},
headers: {
'Content-Type': '${isTextPlain ? 'text/plain' : 'application/json'}',
}
Expand Down Expand Up @@ -129,3 +138,73 @@ export const ${handlerName} = (${isReturnHttpResponse && !isTextPlain ? `overrid
imports: includeResponseImports,
};
};

export const generateMSW = (
generatorVerbOptions: GeneratorVerbOptions,
generatorOptions: GeneratorOptions,
) => {
const { pathRoute, override, mock } = generatorOptions;
const { operationId, response } = generatorVerbOptions;

const route = getRouteMSW(
pathRoute,
override?.mock?.baseUrl ?? (!isFunction(mock) ? mock?.baseUrl : undefined),
);

const handlerName = `get${pascal(operationId)}MockHandler`;
const getResponseMockFunctionName = `get${pascal(operationId)}ResponseMock`;

const baseDefinition = generateDefinition(
'',
route,
getResponseMockFunctionName,
handlerName,
generatorVerbOptions,
generatorOptions,
response.definition.success,
'200',
response.imports,
response.types.success,
response.contentTypes,
);

const mockImplementations = [baseDefinition.implementation.function];
const handlerImplementations = [baseDefinition.implementation.handler];
const imports = [...baseDefinition.imports];

if (
generatorOptions.mock &&
isObject(generatorOptions.mock) &&
generatorOptions.mock.generateEachHttpStatus
) {
[...response.types.success, ...response.types.errors].forEach(
(statusResponse) => {
const definition = generateDefinition(
statusResponse.key,
route,
getResponseMockFunctionName,
handlerName,
generatorVerbOptions,
generatorOptions,
statusResponse.value,
statusResponse.key,
response.imports,
[statusResponse],
[statusResponse.contentType],
);
mockImplementations.push(definition.implementation.function);
handlerImplementations.push(definition.implementation.handler);
imports.push(...definition.imports);
},
);
}

return {
implementation: {
function: mockImplementations.join('\n'),
handlerName: handlerName,
handler: handlerImplementations.join('\n'),
},
imports: imports,
};
};
32 changes: 21 additions & 11 deletions packages/mock/src/msw/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import {
ContextSpecs,
generalJSTypesWithArray,
GeneratorImport,
GetterResponse,
GlobalMockOptions,
isFunction,
MockOptions,
NormalizedOverrideOutput,
resolveRef,
ResReqTypesValue,
stringify,
} from '@orval/core';
import { OpenAPIObject, SchemaObject } from 'openapi3-ts/oas30';
Expand Down Expand Up @@ -116,21 +116,25 @@ const getMockScalarJsTypes = (
export const getResponsesMockDefinition = ({
operationId,
tags,
response,
returnType,
responses,
imports: responseImports,
mockOptionsWithoutFunc,
transformer,
context,
mockOptions,
}: {
operationId: string;
tags: string[];
response: GetterResponse;
returnType: string;
responses: ResReqTypesValue[];
imports: GeneratorImport[];
mockOptionsWithoutFunc: { [key: string]: unknown };
transformer?: (value: unknown, definition: string) => string;
context: ContextSpecs;
mockOptions?: GlobalMockOptions;
}) => {
return response.types.success.reduce(
return responses.reduce(
(
acc,
{ value: definition, originalSchema, example, examples, imports, isRef },
Expand All @@ -147,7 +151,7 @@ export const getResponsesMockDefinition = ({
if (exampleValue) {
acc.definitions.push(
transformer
? transformer(exampleValue, response.definition.success)
? transformer(exampleValue, returnType)
: JSON.stringify(exampleValue),
);
return acc;
Expand All @@ -157,7 +161,7 @@ export const getResponsesMockDefinition = ({
const value = getMockScalarJsTypes(definition, mockOptionsWithoutFunc);

acc.definitions.push(
transformer ? transformer(value, response.definition.success) : value,
transformer ? transformer(value, returnType) : value,
);

return acc;
Expand All @@ -181,7 +185,7 @@ export const getResponsesMockDefinition = ({
context: isRef
? {
...context,
specKey: response.imports[0]?.specKey ?? context.specKey,
specKey: responseImports[0]?.specKey ?? context.specKey,
}
: context,
existingReferencedProperties: [],
Expand All @@ -190,7 +194,7 @@ export const getResponsesMockDefinition = ({
acc.imports.push(...scalar.imports);
acc.definitions.push(
transformer
? transformer(scalar.value, response.definition.success)
? transformer(scalar.value, returnType)
: scalar.value.toString(),
);

Expand All @@ -206,15 +210,19 @@ export const getResponsesMockDefinition = ({
export const getMockDefinition = ({
operationId,
tags,
response,
returnType,
responses,
imports: responseImports,
override,
transformer,
context,
mockOptions,
}: {
operationId: string;
tags: string[];
response: GetterResponse;
returnType: string;
responses: ResReqTypesValue[];
imports: GeneratorImport[];
override: NormalizedOverrideOutput;
transformer?: (value: unknown, definition: string) => string;
context: ContextSpecs;
Expand All @@ -228,7 +236,9 @@ export const getMockDefinition = ({
const { definitions, imports } = getResponsesMockDefinition({
operationId,
tags,
response,
returnType,
responses,
imports: responseImports,
mockOptionsWithoutFunc,
transformer,
context,
Expand Down
11 changes: 11 additions & 0 deletions tests/configs/default.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,15 @@ export default defineConfig({
target: '../generated/default/runtime-mock-delay/endpoints.ts',
},
},
'http-status-mocks': {
input: '../specifications/petstore.yaml',
output: {
mock: {
generateEachHttpStatus: true,
type: 'msw',
},
schemas: '../generated/default/http-status-mocks/model',
target: '../generated/default/http-status-mocks/endpoints.ts',
},
},
});

0 comments on commit 5cab47d

Please sign in to comment.