Open bengry opened 2 months ago
That would make for a nice helper. Is there more to the example where mockedUser
is being returned from a resolver and part of a graphql query?
One thing that makes it difficult, and maybe this doesn't matter in your case, is that with resolver functions you aren't creating individual User
objects. Since first
and last
are individual resolver functions, if first
always returned "John".
The query:
{
users {
first
last
}
}
would return
{
"data": [
{ "first": "John", "last": "Smith" },
{ "first": "John", "last": "Johnson" },
]
}
That would look something like this:
import { setResolver } from 'graphql-mocks/resolver-map';
const createUserMockMiddleware = (hardcodedFirstName) => (resolverMap) => {
setResolver(resolverMap, ["User", "first"], () => hardcodedFirstName);
setResolver(resolverMap, ["User", "last"], () => faker.name());
}
If you wanted something less hardcoded and more representative of instances of users then decoupling the User
from the resolver function to something stateful is helpful. Using graphql-paper
comes in handy. This technique is better described in GraphQL Paper: Separation of Concrete and Derived Data. In this case the concrete data is first
and the derived (dynamic) is last
.
import { Paper } from "graphql-paper";
const paper = new Paper(graphqlSchema);
const userMiddleware = (resolverMap, { dependencies }) => {
setReference(resolverMap, ['User', 'first'], (parent, args, context, info) =>
// return a specific user based on some criteria
dependencies.paper.User.find((user) => user.id === args.id)?.name
);
setReference(resolverMap, ['User', 'last'], () => sinon.name());
};
const handler = new GraphQLHandler({
dependencies: {
graphqlSchema,
paper,
},
});
// if the `async`
beforeEach(async () => {
paper.mutate(({ create }) => {
// create as many specific Users as needed
create("User", { first: "John" });
create("User", { first: "Jessica" });
});
});
The last thing I'll add is that the default resolver for a field is to look at its parent and return the property that matches the field name, so if you have a schema like:
schema {
query: Query
}
type Query {
user: User
users: [User!]!
}
type User {
first: String
last: String
}
const createMockuser = (partial) => {
return {
last: sinon.name(),
...partial,
};
};
const resolverMap = {
Query: {
user: () => createMockUser({ first: "John" })
users: () => [createMockUser({ first: "Bob" }), createMockUser({ first: "Jessica" })]
}
};
This might be a simple way of doing what you're after.
I think GraphQL Paper: Separation of Concrete and Derived Data is probably the closest fit? Let me know if I'm missing something or if this isn't quite what you're after. It might be helpful to see the example as the assertion of mockedUser
involving the resolvers and/or query.
@chadian I think that GraphQL Paper is the realm of what I'm describing, based on what I understand from the docs you linked, but its very specific. It seems like each function needs to be hand-crafted (as explained here). I think my use case overlaps with what Paper does, but I'm not sure it's exactly the same, or similar enough to use as-is.
What I want is a more generic solution - where I give it a GraphQL type + some data I pre-created, and it completes the rest with the same logic and middlewares that are already set on it.
e.g.
// @generated/graphqlTypes.ts
type DateTime = string;
interface Task {
id: String;
title: String;
status: TaskStatus;
completedAt: Maybe<DateTime>;
createdBy: User;
}
interface User { ... }
enum TaskStatus {
Pending = "PENDING",
Completed = "COMPLETED",
Wont_Do = "WONT_DO",
}
// tasks.spec.ts
test("Complete a task", ({ tasksPage, mockServer }) => {
const pendingTask = createMockOf<Task>('Task', {
status: TaskStatus.Pending, // this is the only pieces of information I care about for this test, the rest can be automatically mocked.
});
await mockServer.setupTasks([pendingTask]); // this sets up mocking the network request using msw or whatever
await tasksPage.goto();
await tasksPage.completeTask(task.title);
expect(await tasksPage.findTask(task.title).isCompleted()).toBe(true);
});
What I want to happen behind the scenes when calling createMockOf
is that graphql-mocks
will run my Faker.js-based middleware and create a Task
, along with any nested types it may include¹, to complete the missing fields.
¹ Note that in my example all fields are mocked, without taking the graphql query into account. i.e. even if createdBy
is not requested by the GQL query on the page, pendingTask
will have it. This may be further optimized, but I'm not sure how important it is to only generate the requested fields, in practical use.
My second issue with Paper etc., is the complexity from a consumer's PoV - it's hard (at least for me 😅) to understand what I need to set up, and how things connect to each other. At the end of the day I just want a way to mock GQL types and queries with custom logic and/or hand-picked values, per request. So ideally the API would be simple enough to use and set-up.
I hope I'm explaining myself well enough here, but LMK if you need more examples or use-cases. I'll see what I can derive from our code-base.
As detailed in this gist (also shared in https://github.com/graphql-mocks/graphql-mocks/issues/273), we're using
graphql-mocks
to create fake data for GQL requests.Sometimes though we want to create a semi-fake type. i.e. create a basic type ourselves (similar to what
DeepPartial<SomeGQLType>
would do in TypeScript), and letgraphql-mocks
fill in the rest of the stuff.Ideally something like:
Is such a thing possible? The entire mocking infrastructure already exists, but I'm not sure if there's a userland API for connecting the dots and creating this
createMockOf
function.