Skip to content

Commit aba136e

Browse files
authored
feat: dynamic generation mode (ardeois#86)
Adding a configuration option `dynamic?: boolean` that defaults to false and makes no changes to the output, but when set to true generates mock values dynamically at run time (when a mock function is called), rather than statically at build time (when a mock function is generated). Fixes ardeois#85
1 parent 11c9f23 commit aba136e

File tree

16 files changed

+238
-41
lines changed

16 files changed

+238
-41
lines changed

.eslintignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
dist
2-
node_modules
2+
node_modules

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ yarn-debug.log*
1515
yarn-error.log*
1616

1717
# generated test file
18-
/tests/circular-mocks/mocks.ts
18+
/tests/**/mocks.ts
1919

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ export const aUser = (overrides?: Partial<User>): User => {
129129
130130
When disabled, underscores will be retained for type names when the case is changed. It has no effect if `typenames` is set to `keep`.
131131
132+
### dynamicValues (`boolean`, defaultValue: `false`)
133+
134+
When enabled, values will be generated dynamically when the mock function is called rather than statically when the mock function is generated. The values are generated consistently from a [casual seed](https://github.com/boo1ean/casual#seeding) that can be manually configured using the generated `seedMocks(seed: number)` function, as shown in [this test](https://github.com/JimmyPaolini/graphql-codegen-typescript-mock-data/blob/dynamic-mode/tests/dynamicValues/spec.ts#L13).
135+
132136
## Examples of usage
133137
134138
**codegen.yml**

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ module.exports = {
33
transform: {
44
'^.+\\.tsx?$': 'ts-jest',
55
},
6-
globalSetup: './tests/circular-mocks/create-mocks.ts',
6+
globalSetup: './tests/globalSetup.ts',
77
};

src/index.ts

+34-13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Options<T = TypeNode> = {
2121
currentType: T;
2222
customScalars?: ScalarMap;
2323
transformUnderscore: boolean;
24+
dynamicValues?: boolean;
2425
};
2526

2627
const convertName = (value: string, fn: (v: string) => string, transformUnderscore: boolean): string => {
@@ -102,20 +103,22 @@ const getNamedType = (opts: Options<NamedTypeNode>): string | number | boolean =
102103
return '';
103104
}
104105

105-
casual.seed(hashedString(opts.typeName + opts.fieldName));
106+
if (!opts.dynamicValues) casual.seed(hashedString(opts.typeName + opts.fieldName));
106107
const name = opts.currentType.name.value;
107108
const casedName = createNameConverter(opts.typenamesConvention, opts.transformUnderscore)(name);
108109
switch (name) {
109110
case 'String':
110-
return `'${casual.word}'`;
111+
return opts.dynamicValues ? `casual.word` : `'${casual.word}'`;
111112
case 'Float':
112-
return Math.round(casual.double(0, 10) * 100) / 100;
113+
return opts.dynamicValues
114+
? `Math.round(casual.double(0, 10) * 100) / 100`
115+
: Math.round(casual.double(0, 10) * 100) / 100;
113116
case 'ID':
114-
return `'${casual.uuid}'`;
117+
return opts.dynamicValues ? `casual.uuid` : `'${casual.uuid}'`;
115118
case 'Boolean':
116-
return casual.boolean;
119+
return opts.dynamicValues ? `casual.boolean` : casual.boolean;
117120
case 'Int':
118-
return casual.integer(0, 9999);
121+
return opts.dynamicValues ? `casual.integer(0, 9999)` : casual.integer(0, 9999);
119122
default: {
120123
const foundType = opts.types.find((enumType: TypeItem) => enumType.name === name);
121124
if (foundType) {
@@ -147,9 +150,11 @@ const getNamedType = (opts: Options<NamedTypeNode>): string | number | boolean =
147150
// mapping for this particular scalar
148151
if (!customScalar || !customScalar.generator) {
149152
if (foundType.name === 'Date') {
150-
return `'${new Date(casual.unix_time).toISOString()}'`;
153+
return opts.dynamicValues
154+
? `new Date(casual.unix_time).toISOString()`
155+
: `'${new Date(casual.unix_time).toISOString()}'`;
151156
}
152-
return `'${casual.word}'`;
157+
return opts.dynamicValues ? `casual.word` : `'${casual.word}'`;
153158
}
154159

155160
// If there is a mapping to a `casual` type, then use it and make sure
@@ -163,6 +168,11 @@ const getNamedType = (opts: Options<NamedTypeNode>): string | number | boolean =
163168
const generatorArgs: unknown[] = Array.isArray(customScalar.arguments)
164169
? customScalar.arguments
165170
: [customScalar.arguments];
171+
if (opts.dynamicValues) {
172+
return `casual['${customScalar.generator}']${
173+
typeof embeddedGenerator === 'function' ? `(...${JSON.stringify(generatorArgs)})` : ''
174+
}`;
175+
}
166176
const value =
167177
typeof embeddedGenerator === 'function'
168178
? embeddedGenerator(...generatorArgs)
@@ -316,6 +326,7 @@ export interface TypescriptMocksPluginConfig {
316326
typesPrefix?: string;
317327
enumsPrefix?: string;
318328
transformUnderscore?: boolean;
329+
dynamicValues?: boolean;
319330
}
320331

321332
interface TypeItem {
@@ -396,6 +407,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
396407
currentType: node.type,
397408
customScalars: config.scalars,
398409
transformUnderscore,
410+
dynamicValues: config.dynamicValues,
399411
});
400412

401413
return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`;
@@ -424,6 +436,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
424436
currentType: field.type,
425437
customScalars: config.scalars,
426438
transformUnderscore,
439+
dynamicValues: config.dynamicValues,
427440
});
428441

429442
return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`;
@@ -512,12 +525,20 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
512525
enumsPrefix: config.enumsPrefix,
513526
transformUnderscore: transformUnderscore,
514527
});
515-
// List of function that will generate the mock.
528+
// Function that will generate the mocks.
516529
// We generate it after having visited because we need to distinct types from enums
517530
const mockFns = definitions
518531
.map(({ mockFn }: { mockFn: () => string }) => mockFn)
519-
.filter((mockFn: () => string) => !!mockFn);
520-
521-
return `${typesFileImport}${mockFns.map((mockFn: () => string) => mockFn()).join('\n')}
522-
`;
532+
.filter((mockFn: () => string) => !!mockFn)
533+
.map((mockFn: () => string) => mockFn())
534+
.join('\n');
535+
536+
let mockFile = '';
537+
if (config.dynamicValues) mockFile += "import casual from 'casual';\n";
538+
mockFile += typesFileImport;
539+
if (config.dynamicValues) mockFile += '\ncasual.seed(0);\n';
540+
mockFile += mockFns;
541+
if (config.dynamicValues) mockFile += '\n\nexport const seedMocks = (seed: number) => casual.seed(seed);';
542+
mockFile += '\n';
543+
return mockFile;
523544
};

tests/__snapshots__/typescript-mock-data.spec.ts.snap

+77
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,83 @@ export const aQuery = (overrides?: Partial<Query>): Query => {
795795
"
796796
`;
797797

798+
exports[`should generate dynamic values in mocks 1`] = `
799+
"import casual from 'casual';
800+
801+
casual.seed(0);
802+
803+
export const anAvatar = (overrides?: Partial<Avatar>): Avatar => {
804+
return {
805+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : casual.uuid,
806+
url: overrides && overrides.hasOwnProperty('url') ? overrides.url! : casual.word,
807+
};
808+
};
809+
810+
export const aUser = (overrides?: Partial<User>): User => {
811+
return {
812+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : casual.uuid,
813+
creationDate: overrides && overrides.hasOwnProperty('creationDate') ? overrides.creationDate! : new Date(casual.unix_time).toISOString(),
814+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : casual.word,
815+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
816+
status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online,
817+
customStatus: overrides && overrides.hasOwnProperty('customStatus') ? overrides.customStatus! : AbcStatus.HasXyzStatus,
818+
scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : casual.word,
819+
camelCaseThing: overrides && overrides.hasOwnProperty('camelCaseThing') ? overrides.camelCaseThing! : aCamelCaseThing(),
820+
unionThing: overrides && overrides.hasOwnProperty('unionThing') ? overrides.unionThing! : anAvatar(),
821+
prefixedEnum: overrides && overrides.hasOwnProperty('prefixedEnum') ? overrides.prefixedEnum! : PrefixedEnum.PrefixedValue,
822+
};
823+
};
824+
825+
export const aWithAvatar = (overrides?: Partial<WithAvatar>): WithAvatar => {
826+
return {
827+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : casual.uuid,
828+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
829+
};
830+
};
831+
832+
export const aCamelCaseThing = (overrides?: Partial<CamelCaseThing>): CamelCaseThing => {
833+
return {
834+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : casual.uuid,
835+
};
836+
};
837+
838+
export const aPrefixedResponse = (overrides?: Partial<PrefixedResponse>): PrefixedResponse => {
839+
return {
840+
ping: overrides && overrides.hasOwnProperty('ping') ? overrides.ping! : casual.word,
841+
};
842+
};
843+
844+
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {
845+
return {
846+
abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : casual.word,
847+
};
848+
};
849+
850+
export const anUpdateUserInput = (overrides?: Partial<UpdateUserInput>): UpdateUserInput => {
851+
return {
852+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : casual.uuid,
853+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : casual.word,
854+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
855+
};
856+
};
857+
858+
export const aMutation = (overrides?: Partial<Mutation>): Mutation => {
859+
return {
860+
updateUser: overrides && overrides.hasOwnProperty('updateUser') ? overrides.updateUser! : aUser(),
861+
};
862+
};
863+
864+
export const aQuery = (overrides?: Partial<Query>): Query => {
865+
return {
866+
user: overrides && overrides.hasOwnProperty('user') ? overrides.user! : aUser(),
867+
prefixed_query: overrides && overrides.hasOwnProperty('prefixed_query') ? overrides.prefixed_query! : aPrefixedResponse(),
868+
};
869+
};
870+
871+
export const seedMocks = (seed: number) => casual.seed(seed);
872+
"
873+
`;
874+
798875
exports[`should generate mock data functions 1`] = `
799876
"
800877
export const anAvatar = (overrides?: Partial<Avatar>): Avatar => {

tests/circular-mocks/create-mocks.ts

-25
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`should generate dynamic values when dynamicValues is true 1`] = `
4+
Object {
5+
"id": "99bd9d8d-79a6-474e-8f46-6cc8796ed151",
6+
"obj": Object {
7+
"bool": false,
8+
"flt": 3.68,
9+
"int": 202,
10+
},
11+
"str": "est",
12+
}
13+
`;
14+
15+
exports[`should generate dynamic values when dynamicValues is true 2`] = `
16+
Object {
17+
"id": "ec2ddf7c-d78c-4a1b-a928-ec816742cb73",
18+
"obj": Object {
19+
"bool": true,
20+
"flt": 0.19,
21+
"int": 1352,
22+
},
23+
"str": "similique",
24+
}
25+
`;

tests/dynamicValues/schema.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { buildSchema } from 'graphql';
2+
3+
export default buildSchema(/* GraphQL */ `
4+
type A {
5+
id: ID!
6+
str: String!
7+
obj: B!
8+
}
9+
10+
type B {
11+
int: Int!
12+
flt: Float!
13+
bool: Boolean!
14+
}
15+
`);

tests/dynamicValues/spec.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { anA, seedMocks } from './mocks';
2+
3+
it('should generate dynamic values when dynamicValues is true', () => {
4+
const a1 = anA();
5+
expect(a1).toMatchSnapshot();
6+
7+
const a2 = anA();
8+
expect(a2).toMatchSnapshot();
9+
10+
expect(a1).not.toEqual(a2);
11+
});
12+
13+
it('should generate dynamic values from seed when dynamicValues is true', () => {
14+
seedMocks(0);
15+
const a1 = anA();
16+
17+
seedMocks(0);
18+
const a1Copy = anA();
19+
20+
seedMocks(1);
21+
const a2 = anA();
22+
23+
expect(a1).toEqual(a1Copy);
24+
expect(a1).not.toEqual(a2);
25+
expect(a1Copy).not.toEqual(a2);
26+
});

tests/dynamicValues/types.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export type A = {
2+
id: string;
3+
str: string;
4+
obj: B;
5+
};
6+
7+
export type B = {
8+
int: number;
9+
flt: number;
10+
bool: boolean;
11+
};

tests/globalSetup.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import fs from 'fs';
2+
import { plugin } from '../src';
3+
import circularRelationshipsSchema from './terminateCircularRelationships/schema';
4+
import dynamicValuesSchema from './dynamicValues/schema';
5+
6+
export default async () => {
7+
const terminateCircularRelationshipsMocks = await plugin(circularRelationshipsSchema, [], {
8+
typesFile: './types.ts',
9+
terminateCircularRelationships: true,
10+
});
11+
fs.writeFileSync('./tests/terminateCircularRelationships/mocks.ts', terminateCircularRelationshipsMocks.toString());
12+
13+
const dynamicValuesMocks = await plugin(dynamicValuesSchema, [], {
14+
typesFile: './types.ts',
15+
dynamicValues: true,
16+
});
17+
fs.writeFileSync('./tests/dynamicValues/mocks.ts', dynamicValuesMocks.toString());
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { buildSchema } from 'graphql';
2+
3+
export default buildSchema(/* GraphQL */ `
4+
type A {
5+
B: B!
6+
C: C!
7+
}
8+
type B {
9+
A: A!
10+
}
11+
type C {
12+
aCollection: [A!]!
13+
}
14+
type D {
15+
A: A!
16+
B: B!
17+
}
18+
`);
File renamed without changes.
File renamed without changes.

tests/typescript-mock-data.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,10 @@ it('should preserve underscores if transformUnderscore is false', async () => {
389389
);
390390
expect(result).toMatchSnapshot();
391391
});
392+
393+
it('should generate dynamic values in mocks', async () => {
394+
const result = await plugin(testSchema, [], { dynamicValues: true });
395+
396+
expect(result).toBeDefined();
397+
expect(result).toMatchSnapshot();
398+
});

0 commit comments

Comments
 (0)