graphql-data-generator
v0.2.4
Published
A tool to generate objects and operation mocks from a GraphQL schema. Allows defining _transforms_ to simplify common variations.
Downloads
420
Readme
graphql-data-generator
A tool to generate objects and operation mocks from a GraphQL schema. Allows defining transforms to simplify common variations.
Example
First generate types and type lists:
npx graphql-data-generator --schema src/graphql/schema.graphql --outfile src/util/test/types.ts
Then consume these types and initialize a builder:
import { readFileSync } from "node:fs";
import { init, Patch } from "npm:graphql-data-generator";
import {
Inputs,
inputs,
Mutation,
mutations,
queries,
Query,
Subscription,
subscriptions,
Types,
types,
} from "./types.ts";
const schema = readFileSync("graphql/schema.graphql", "utf-8");
const scalars = {
ID: (typename) => `${typename.toLowerCase()}-0`,
String: "",
};
export const build = init<Query, Mutation, Subscription, Types, Inputs>(
schema,
queries,
mutations,
subscriptions,
types,
inputs,
scalars,
)(() => ({
// Can define transforms for objects
User: {
// `default` automatically applies to all User objects
default: { profilePicture: (u) => `https://example.com/${u.id}.png` },
// Can invoke with `build.User.withPost()` or `build.User().withPost()`
withPost: (_p, post: Patch<Types["Post"]> = {}) => ({
posts: { next: post },
}),
},
// Can define transforms for operations
CreatePost: {
withAuthorId: (_, authorId: string) => ({
variables: { input: { authorId } },
data: { createPost: { author: { id: authorId } } },
}),
},
}));
After which you can build objects and operations in your tests:
import { build } from "util/tests/build.ts";
const user1 = build.User().withPost();
// Can override properties
const user2 = build.User({ id: "user-2", email: (u) => u.email + "2" });
// `patch` is a built-in transform while `withPost` was defined above
const user3 = user1.patch({
id: "user-3",
// `last` is special property for arrays to modify the last element in the array. If one does not exist it is created
// `next` is a special property for arrays to append a new item to the array
posts: { last: { author: { id: "user-3" } }, next: {} },
});
const createPost = build.CreatePost({ data: { createPost: { id: "post-id" } } })
.withAuthorId("user-3");
CLI options
banner=file|copy
: Places copy at the beginning of the generated output. If a file path is detected, will use the contents of the file.enums=[enums|literals|none|import:*]
: Describes how enums are generated.enums
: Use TypeScript enum. E.g.,enum Status { Draft, Submitted }
literals
: Use string literals. E.g.,type Status = "Draft" | "Submitted";
none
: Skip generating of enums all together.import:file
Import enums from the provided path. E.g.,import { Status } from "file";
exports=[operations|types]
: Toggle exporting of operations and/or types.namingConvention=NamingConvention
: Specify aNamingConvention
for generated types. Defaults tochange-case-all#pascalCase
if using the plugin, otherwise defaults tokeep
.notypenames
: Toggle automatic inclusion of__typename
.operations=dir
: Restrict generation of operations to a specific directory. Can be used multiple times for multiple directories.outfile=file
: Switch output to right tofile
instead of stdout.scalar=Scalar:Type
: Specify the type of an individual scalar. E.g.,type Scalar = Type;
scalars=file.json
: Specify multiple scalars from a json file.schema=file
: Specify the GraphQL schema file to use.typesFile=file
: Specify file generated by@graphql-codegen
to import types from.
@graphql-codegen plugin
A plugin shim exists for
@graphql-codegen
:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "src/schema.graphql",
documents: ["src/**/*.gql", "src/**/*.ts"],
generates: {
"./src/graphql/": {
preset: "client",
config: {
scalars: {
DateTime: "string",
URL: "string",
},
},
},
"./src/build/generated.ts": {
plugins: ["graphql-data-generator/plugin"],
config: {
typesFile: "../graphql/graphql.js",
scalars: {
DateTime: "string",
URL: "string",
},
banner: "// generated\n",
},
},
},
};
export default config;
Specifying a typesFile
will skip outputting generated types and will instead
depend on the types generated by @graphql-codegen
itself. The generated.ts
file can then be consumed by a build
script similar to the above example.
Patches
A patch
is similar to a DeepPartial
with a few extensions. First, functions
can be passed instead of literal properties. These functions will be invoked
during instantiation and will receieve the previous host value as a property:
type Thing = { foo: string };
type ThingPatch = Patch<Thing>;
// Can exclude properties
const patch1: ThingPatch = {};
// Can specify them
const patch2: ThingPatch = { foo: "ok" };
// undefined will be ignored
const patch3: ThingPatch = { foo: undefined };
// Can use a function for more dynamic values
const patch4: ThingPatch = { foo: (prev: Thing) => `${prev.foo}2` };
Patch
also has added semantics for arrays, including an object notation:
type Container = { values: string[] };
type ContainerPatch = Patch<Container>;
// Directly set index 1
const patch1: ContainerPatch = { values: { 1: "ok" } };
// `last` will modify the last element in the array. If the array is empty,
// instantiates a new element.
const patch2: ContainerPatch = { values: { last: "ok" } };
// `next` instantiates a new element and appends it to the array.
const patch3: ContainerPatch = { values: { next: "ok" } };
// `length` can be used to truncate or instantiate new elements
const patch4: ContainerPatch = { values: { length: 0 } };
// An array can be directly used. Will truncate extra elements.
const patch5: ContainerPatch = { values: ["ok"] };
Transforms
graphql-data-generator
creates objects and operations by applying patches
in sequence. A patch is similar to a DeepPartial
, but supports functions
for each property and has . Transforms are a mechanism to define shorthands for
common patches for particular objects or operations. There are several built in
transforms:
Objects:
default
: A special transform that is automatically called for all instantations.patch
: Accepts a list of patches
Operations:
patch
: Accepts a list of patchesvariables
: Accepts an operation variable patchdata
: Accepts an operation data patch
When defining custom transforms, the default
transform has special meaning: it
will be automatically applied as the first aptch to all instances.
Extra
The init
function supports a 6th optional generic parameter, Extra, which
allows defining extra properties for operation mocks, passable in operation
patches. This is helpful to support extra Apollo-related properties or custom
logic. Extra properties will always be optional in patches and the final object
and will not be patched in but simply merged, such as by Object.assign
.
Example: Adding an extra optional property to bypass an assertion a mock is used
const build = init<
Query,
Mutation,
Subscription,
Types,
Inputs,
{ optional: boolean }
>(
schema,
queries,
mutations,
subscriptions,
types,
inputs,
scalars,
)(() => ({}));
build.CreatePost({ optional: true }).optional; // true
finalizeOperation
The init
's final parmaeter, options
, supports a finalizeOperation
key.
This is used as final step when building an operation and acts as a generic
transform on the final mock itself, which can be useful to attach spies or when
building interactivity with other GQL tools.
Exmaple: Enforcing a mock is used in Apollo & Jest
const build = init<Query, Mutation, Subscription, Types, Inputs>(
schema,
queries,
mutations,
subscriptions,
types,
inputs,
scalars,
{
finalizeOperation: (op) => {
const fn = Object.assign(
jest.fn(() => op.result),
op.result,
) as typeof op["result"];
op.result = fn;
return op;
},
},
)(() => ({}));
const createPost = build.CreatePost();
// ...
expect(createPost.result).toHaveBeenCalled();