@kindly/graphql-ergonomock
v1.0.0
Published
Automatic mocking of GraphQL operations.
Downloads
507
Readme
Table of Contents
- Table of Contents
- About The Project
- Getting Started
- API
- Roadmap
- Contributing
- License
- Contact
- Acknowledgements
About The Project
This library provides a developer-friendly method to mock your GraphQL requests. By default, it ships with the following features:
- Automatic mocking of types and fields based on schema definition.
- Deterministic randomization of mocked values based on the provided mock shape, to support usage of features such as snapshots.
- Support for queries using fragments, unions & interfaces.
- Allows usage of functions as mock values, which have the signature of a GraphQL resolver, and are resolved at runtime.
Basic Example
import { ergonomock } from 'graphql-ergonomock';
// Given a particular GraphQL Schema, ...
const schema = gql`
type Shape {
id: ID!
returnInt: Int
nestedShape: Shape
returnBoolean: Boolean
}
type Query {
getShape: Shape
}
`;
// ...a GraphQL query, ...
const query = gql`
{
getShape {
id
nestedShape {
returnInt
nestedShape {
returnInt
returnBoolean
}
}
}
}
`;
// ...and a partial, possibly nested shape of data...
const mocks = {
getShape: {
nestedShape: {
returnInt: 5,
nestedShape: {
returnInt: 10
}
}
}
};
// ... provides the expected object filled with mock data
const resp = ergonomock(schema, query, { mocks });
expect(resp.data).toMatchObject({
id: expect.toBeString(),
nestedShape: {
returnInt: 5,
nestedShape: {
returnInt: 10,
returnBoolean: expect.toBeBoolean()
}
}
}); // ✅ test pass
Basic Example (Apollo)
import { ErgonoMockedProvider as MockedProvider } from "graphql-ergonomock";
// Given a particular GraphQL Schema, ...
const schema = gql`
type Shape {
id: ID!
returnString: String
returnBoolean: Boolean
}
type Query {
getShape: Shape
}
`;
// ...a component making a query ...
const QUERY = gql`
query MyQuery {
getShape {
id
returnString
returnBoolean
}
}
`;
const MyComponent = () => {
const { loading, error, data } = useQuery(QUERY);
if (loading) return <p>Loading</p>;
if (error) return <p>Error</p>;
return (
<div>
MyComponent.
<div>String: {data.getShape.returnString}</div>
<div>Boolean: {data.getShape.returnBoolean.toString()}</div>
</div>
);
};
test("MyComponent can render the query results.", async () => {
const mocks = {
MyQuery: {
getShape: { returnString: "John Doe" }
}
};
const { findByText } = render(
<MockedProvider schema={schema} mocks={mocks}>
<MyComponent />
</MockedProvider>
);
expect(await findByText(/String: John Doe/)).toBeVisible();
expect(await findByText(/Boolean: (true|false)/)).toBeVisible();
}); // ✅ test pass
Built With
Getting Started
Installation
npm i graphql-ergonomock --save-dev
Usage
Default Case
The ergonomock()
function can be used on its own to create mock responses out of GraphQL queries.
import { ergonomock } from 'graphql-ergonomock';
import schema from '../my-schema.graphql';
test("I can mock a response", () => {
const resp = ergonomock(schema, 'query MyCrucialOperation { car { id, plateNumber } }', {
mocks: { car: { plateNumber: '123abc' } }
});
expect(resp).toMatchObject({
data: {
car: {
id: expect.toBeString(),
plateNumber: '123abc'
}
}
});
});
ErgonoMockedProvider for Use with Apollo-Client
If your app uses Apollo-Client, you can also use ErgonoMockedProvider
which wraps ergonomock()
. The generated values are stable & deterministic based on the query name, shape, and variables (this is so you can leverage testing snapshots if you are so inclined).
import { ErgonoMockedProvider as MockedProvider } from 'graphql-ergonomock';
import schema from '../my-schema.graphql';
test("I can mock a response", () => {
const mocks = {
MyCrucialOperation: {
car: { plateNumber: '123abc' }
}
};
const { findByText } = render(
<MockedProvider schema={schema} mocks={mocks}>
<MyApp />
</MockedProvider>
);
expect(await findByText(/'123abc'/)).toBeVisible();
});
If a particular component is called multiple times in the React tree (but with different variables), you can provide a function as value for an operation, and the function will be called with the operation: GraphQLOperation
as first and only argument.
import { ErgonoMockedProvider as MockedProvider } from 'graphql-ergonomock';
import schema from '../my-schema.graphql';
test("I can mock a response with a function", () => {
let cardinality = 0;
const mocks = {
MyCrucialOperation: (operation) => ({ plateNumber: `aaa00${cardinality++}` })
};
const { findAllByText } = render(
<MockedProvider schema={schema} mocks={mocks}>
<MyApp />
</MockedProvider>
);
expect(await findAllByText(/'aaa00'/)).toHaveLength(3);
});
Finally, you can spy on executed operations via the onCall
prop.
const spy = jest.fn();
const { findByText } = render(
<MockedProvider schema={schema} onCall={spy}>
<MyComponent id="1" />
<MyComponent id="2" />
<MyComponent id="3" />
</MockedProvider>
);
//...
expect(spy.mock.calls).toHaveLength(3);
const { operation, response } = spy.mock.calls[0][0];
expect(operation.variables.id).toEqual("1");
Providing Functions as Resolver in the Mock Shape
You can use functions for any part of the nested mock, and it will be used as a resolver for this field. The typical signature for a resolver is (root, args, context, info) => any
.
// ...
const mocks = {
returnShape: {
returnInt: 4321,
nestedShape: (root: any, args: any, ctx: any, info: any) => {
return {
returnInt: root.someField ? 1234 : 5678
};
}
}
}
//...
Providing default mock resolver functions
If you have custom scalar types or would like to provide default mock resolver functions for certain types, you can pass your mock functions via the resolvers
option.
const schema = gql`
type Shape = {
id: ID!
returnInt: Int
date: Date
nestedShape: Shape
}
type Query {
getShape: Shape
}
`;
const testQuery = gql`
{
getShape {
id
returnInt
date
nestedShape {
date
}
}
}
`;
const resp = ergonomock(schema, testQuery, {
resolvers: {
Date: () => "2021-04-09"
}
});
expect(resp.data).toMatchObject({
id: expect.toBeString(),
returnInt: expect.toBeNumber(),
date: '2021-04-09'
nestedShape {
date: '2021-04-09'
}
});
Mocking Errors
You can return or throw errors within the mock shape.
const testQuery = gql`
{
getCar {
id
}
}
`;
const resp = ergonomock(schema, testQuery, {
mocks: {
getCar: () => { throw new Error("Server Error"); }
// or simply getCar: new Error("Server Error")
}
});
console.log(resp.data.getCar); // null
console.log(resp.errors[0]); // { message: "Server Error", ...}
Mocking Mutations
You can mock mutations, but if you are using ergonomock()
directly, you'll have to provide the correct variables for the operation in order for it to not fail.
test("Can partially mock mutations", () => {
const query = /* GraphQL */ `
mutation SampleQuery($input: ShapeInput!) {
createShape(input: $input) {
id
returnInt
returnString
}
}
`;
const resp: any = ergonomock(schema, query, {
mocks: {
createShape: { id: "567" }
},
variables: {
input: { someID: "123", someInt: 123 }
}
});
expect(resp.data.createShape).toMatchObject({
id: "567",
returnInt: expect.toBeNumber(),
returnString: expect.toBeString()
});
});
API
ergonomock()
The ergonomock(schema, query, options)
function takes 3 arguments
- (required) A GraphQL schema (not in SDL form).
- (required) A GraphQL query, either in SDL (string) form, or
DocumentNode
. - (optional) An option object.
mocks
is an object that can partially or fully match the expected response shape.seed
is a string used to seed the random number generator for automocked values.variables
is the variable values object used in the query or mutation.
<ErgonoMockedProvider>
This component's props are very similar to Apollo-Client's MockedProvider. The only differences are:
mocks
is an object where keys are the operation names and the values are themocks
input thatergonomock()
would accept. (i.e. could be empty, or any shape that matches the expected response.)onCall
is a handler that gets called by any executed query. The call signature is({operation: GraphQLOperation, response: any}) => void
where response is the full response being returned to that single query. The purpose ofonCall
is to provide some sort of spy (orjest.fn()
) to make assertions on which calls went through, with which variables, and get a handle on the generated values fromergonomock()
.resolvers
is an object where the keys are thegql
type and the values are the mock resolver functions. It's designed to allow users to override or provide a default mock function for certain types. (e.g. custom scalar types) The call signature is(root, args, context, info) => any
.
Roadmap
See the open issues for a list of proposed features (and known issues).
Contributing
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
License
Distributed under the MIT License. See LICENSE
for more information.
Contact
Maintainer: Joel Marcotte (Github @joual)
Project Link: https://github.com/SurveyMonkey/graphql-ergonomock