How to partially mock a schema in GraphQL and Apollo? - graphql

I'm trying to mock only a small portion of the Contact type below. My resolvers return data from a REST endpoint for all fields in Contact except for test. For demo purposes, I want to be able to retain the server data for all other fields, but only mock the test field.
I have the following GraphQL schema defined:
const typeDefs = `
type Contact {
id: String,
first_name: String
last_name: String
middle_name: String
date_of_birth: String
test: String
}
type Query {
contacts: [Contact!]!
Contact(id: String!): Contact!
}
`;
I have the following mocks defined:
const mocks = {
Contact: () => ({
test: () => "This data is mocked!"
})
};
And the following resolvers defined:
const resolvers = {
Query: {
contacts: async (parent, args, { dataSources }) =>
dataSources.MYAPI.getAllContacts(),
Contact: async (parent, { id }, { dataSources }) =>
dataSources.MYAPI.getContact(id)
}
};
Then I initialize the server with:
const server = new ApolloServer({
typeDefs,
resolvers,
mocks,
dataSources: () => {
return {
MYAPI: new MYAPI()
};
},
mockEntireSchema: false
});
The above does not work. I added the mockEntireSchema:true configuration which prevented my server response from being overridden, but the test attribute still returns the default String mock of Hello World instead of my attempted mock of This data is mocked!.
I know the mock is set up correctly because I can remove the mockEntireSchema config and my mock data appears correctly.
Is this even possible or does the behavior of mockEntireSchema and mocks in general not support this?

According to the documentation, you want to keep mockEntireSchema as false and create a mocks object that has the components in it that you still WANT to mock. All other resolvers will be used as they exist. Any mocks that you HAVE defined, though, will be used, so the query resolvers that return Contact types will never be used, since you have defined Contact as a mock.

I now believe that this is actually a bug in Apollo https://github.com/apollographql/graphql-tools/issues/1114. When you use preserveResolvers: true or mockEntireSchema: false (they are the same), it will not overwrite existing resolvers, however, I filtered those resolvers out based on my mock configuration, so they are not loaded in the first place.
This makes the partial mocking work in principle. The bug however is that nested () => MockList(100) calls throw an error in the graphql package because graphql interprets the MockList object as "not an iterable". This doesn't happen with preserveResolvers: false

I haven't tried it but to me it seems like it should work (what you describe). But since it doesn't, a possible workaround would be to just add a field resolver for the test field:
const resolvers = {
Contact: {
test: () => "This data is not mocked but the effect is the same!"
},
Query: {
contacts: async (parent, args, { dataSources }) =>
dataSources.MYAPI.getAllContacts(),
Contact: async (parent, { id }, { dataSources }) =>
dataSources.MYAPI.getContact(id)
}
};

Related

How to organize GraphQL resolver for additional fields

Let's say I have a simple GraphQL type for a user:
type User {
id: ID!
name: String!
}
Query {
user(id:ID!)
}
and a resolver
user = (_, {id}, {api})=> api.getUser(id)
Now I have add a new field to the User called friends and added a new resolver for the User.friends field.
friends = ({id}, _, {api})=> api.getFriends(id)
So now I wonder when we made a query like this, how can I prevent the call to api.getUser but only call api.getFriends.
query {
user(id){
friends {
name
}
}
}
My understanding is that having a resolver defined for the user field in the Query type, it will always call this resolver first and after that all resolvers for fields in the User type.
This is a common problem and there is for example this solution out there: https://github.com/gajus/graphql-lazyloader
Check out the README of the project for a structured description of your problem.
Alternatively, you can implement your own class that contains a cached value making use of how GraphQL.js implements default resolvers:
class User {
constructor(id) {
this.id = id;
}
getInstance({ api }) {
if (!this.instance) {
this.instance = api.getUser(this.id);
}
return this.instance;
}
// notice how id is already a property of this class
name(args, ctx) {
return this.getInstance(ctx).then(instance => instance.name);
}
// do the same for other fields, user will only be fetched once.
friends(args, { api }) {
return api.getFriends(this.id);
}
}
const resolvers = {
Query: {
user: (args) => new User(args.id),
}
}
If you use dataloader you can even do this with even less code thanks to caching in dataloader:
// You probably have this function already somewhere in your apollo server creation
function createContext({ api }) {
return {
api,
loaders: {
user: new Dataloader((ids) => ids.map(id => api.getUser(id))),
},
}
}
const resolvers = {
Query: {
user: (parent, args) => ({ id: args.id }),
},
User: {
name: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.name),
otherProp: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.otherProp),
friends: ({ id }, args, { api })=> api.getFriends(id),
}
}
Dataloader will, even when called twice, only reach to the API once. An added benefit is, that it will cache the value. Ideally, you even provide a batch load function in the API to make the loader even more efficient.
Be aware, that user.fields.name now makes calls for every friend to the API. To avoid that, you could check if the property exists:
name: (parent, args, { loaders }) =>
parent.name ?? loaders.user.load(parent.id).then(user => user.name),

Why are my Apollo cache updates not reflected in my queries?

I am trying to use Apollo cache for local state management to store the state of a form so it can be returned to without clearing.
I am experiencing a problem where the cache is being updated but subsequent queries to the cache are returning stale data. I have experienced this problem in React components using the useQuery hook, and also in Apollo DevTools which I will use to demonstrate it below:
I have this mutation and query set in my resolvers (I am using Typescript):
const resolvers = {
Mutation: {
storeLetterDraft: (_root, args: { type: string, details: LetterSending }, { client, getCacheKey }) => {
const id = getCacheKey({
__typename: "LetterDraft",
id: args.type,
});
const data = { ...args.details };
client.writeFragment({
data,
id,
fragment: LETTER_SENDING_FRAGMENT,
});
},
},
Query: {
letterDraft: (_root, args: { type: string }, { client, getCacheKey }) => {
// I HAVE TRIED A DEBUGGER STATEMENT HERE
const id = getCacheKey({
__typename: "LetterDraft",
id: args.type,
});
return client.readFragment({
id,
fragment: LETTER_SENDING_FRAGMENT,
});
},
},
}
My fragment is:
export const LETTER_SENDING_FRAGMENT = gql`
fragment DraftLetterSending on LetterDraft {
date
firstName
lastName
addressLine1
addressLine2
addressTown
addressCounty
addressPostcode
}
`;
I am initialising my cache with:
cache.writeData({
data: {
letterDrafts: [{
__typename: "LetterDraft",
id: "CREATE",
addressCounty: "Northamptonshire",
addressLine1: "1 Watkin Terrace",
addressLine2: "",
addressPostcode: "NN1 3ER",
addressTown: "Northampton",
date: "2019-11-01",
firstName: "d",
lastName: "d",
}],
},
});
My mutation looks like:
export const storeCreateLetterSendingMutation = gql`
mutation StoreCreateLetterSending($details: LetterSending!) {
storeLetterDraft(type: "CREATE", details: $details) #client
}
`;
Before mutation, the cache in Apollo DevTools looks as expected:
And a query returns as expected:
After the mutation is performed, the cache updates:
However, running the query again results in the stale data:
Interestingly if I put a debugger statement in the part above (I HAVE TRIED A DEBUGGER STATEMENT HERE), then it seems the query resolver is run the first time, but not the second time, so it appears the query is being cached - even though it is the cache I am updating! Therefore I think the issue is with the query not running the resolver subsequently.
I had missed this from the documentation (there are various places on the Apollo website detailing the local cache and #client.
https://www.apollographql.com/docs/react/essentials/local-state/#forcing-resolvers-with-clientalways-true
While leveraging the cache for both local and remote results can be super helpful in a lot of cases, it's not always the best fit. We might want to use a local resolver to calculate a dynamic value that needs to be refreshed on every request, while at the same time continue to use the cache for the network based parts of our query. To support this use case, Apollo Client's #client directive accepts an always argument, that when set to true will ensure that the associated local resolver is run on every request.

How to define client side schema in `react-apollo` application?

I am using react-apollo in my react applicant and I can't figure out how to implement a client side schema.
I have below type definition:
export const alertTypeDefs = gql`
type Query {
alert: Alert
}
type Alert {
message: String!
type: String!
duration: Int!
}
`;
It defines a Query which returns an alert object.
Below is the code I want to use this query.
const cache = new InMemoryCache();
export const createClient = () => {
return new ApolloClient({
cache,
typeDefs: [alertTypeDefs]
});
};
First I initialised a ApolloClient instance with memory cache and alertTypeDefs defined above. Then below is the code to run the query:
const client = createClient();
const data = client.readQuery({query: gql`
{
alert #client
}
`});
console.log('data:', data);
But I got this error Missing selection set for object of type Alert returned for query field alert when run readQuery on the client instance. It seems that the Alert is not defined. But I already defined the Alert query in the typeDefs. It works fine if I change the query code to below which I have to specify what to be returned inside { message }. But it doesn't seem to use the schema. What I expect is that I don't need to specify the return fields if it returns all fields in the schema object. Do I mis-understand the schema?
const data = client.readQuery({query: gql`
{
alert #client {
message
}
}
`});
console.log('data:', data);
If I have to specify the return fields one by one, what the point to define the schema?
This is expected behavior with GraphQL. You always need to specify inside the query which fields you're expecting. So in order to receive all the data you add the fields to the query:
const data = client.readQuery({query: gql`
{
alert #client {
message
type
duration
}
}
`});
console.log('data:', data);
There is an open issue inside the GraphQL specs.
You can define a fragment with all the fields of the entity and then reuse it.
Like this
fragment AllAlertFields on Alert {
message
type
duration
}
And then in a query
query {
allAlerts {
...AllAlertFields
}
}
More details: https://www.apollographql.com/docs/react/data/fragments/

Pass ID from React to Apollo to find correct result?

Im using React with Apollo (Apollo Client v2). I have group query which needs to return a single group.
This code is working but I've hard coded HARD-CODED-ID. How can I instead pass the ID as a string from the React component?
In my React component:
const groupQuery = gql`
query Group {
group {
_id
name
}
}
`;
export default graphql(groupQuery, {
props: ({ data }) => ({ ...data }),
})(GroupPage);
My resolver:
Query: {
groups() {
return Groups.find().fetch();
},
group() {
return Groups.findOne('HARD-CODED-ID');
},
}
There's three things that you'll need to do:
1.) If you haven't already, modify the schema on your server so that your query accepts the id as an input, for example:
type Query {
#other queries
group(id: ID!): Group
}
2.) Modify your resolver so that it handles the passed-in id. Assuming you're using graphql-tools:
group(root, { id }) {
return Groups.findOne(id); // did you mean something like findOne({id}) ?
},
3.) Modify your client-side code. Typically, you'll make the id a prop you pass in to your component, and then use that as a variable in your request.
const groupQuery = gql`
query Group($id: ID!) {
group(id: $id) {
_id
name
}
}
`;
// assuming that the component prop is called groupId
export default graphql(groupQuery, {
options: ({ groupId }) => ({
variables: { id: groupId },
}),
})(GroupPage);
Instead of an object, options can be a function, in which case it's passed the component's props as its first parameter. You can then use those props to define the variables your query will use. You can read more about using variables with Apollo client here and here.

Using graphql-tools to mock a GraphQL server seems broken

I've followed the documentation about using graphql-tools to mock a GraphQL server, however this throws an error for custom types, such as:
Expected a value of type "JSON" but received: [object Object]
The graphql-tools documentation about mocking explicitly states that they support custom types, and even provide an example of using the GraphQLJSON custom type from the graphql-type-json project.
I've provided a demo of a solution on github which uses graphql-tools to successfully mock a GraphQL server, but this relies on monkey-patching the built schema:
// Here we Monkey-patch the schema, as otherwise it will fall back
// to the default serialize which simply returns null.
schema._typeMap.JSON._scalarConfig.serialize = () => {
return { result: 'mocking JSON monkey-patched' }
}
schema._typeMap.MyCustomScalar._scalarConfig.serialize = () => {
return mocks.MyCustomScalar()
}
Possibly I'm doing something wrong in my demo, but without the monkey-patched code above I get the error regarding custom types mentioned above.
Does anyone have a better solution than my demo, or any clues as to what I might be doing wrong, and how I can change the code so that the demo works without monkey-patching the schema?
The relevant code in the demo index.js is as follows:
/*
** As per:
** http://dev.apollodata.com/tools/graphql-tools/mocking.html
** Note that there are references on the web to graphql-tools.mockServer,
** but these seem to be out of date.
*/
const { graphql, GraphQLScalarType } = require('graphql');
const { makeExecutableSchema, addMockFunctionsToSchema } = require('graphql-tools');
const GraphQLJSON = require('graphql-type-json');
const myCustomScalarType = new GraphQLScalarType({
name: 'MyCustomScalar',
description: 'Description of my custom scalar type',
serialize(value) {
let result;
// Implement your own behavior here by setting the 'result' variable
result = value || "I am the results of myCustomScalarType.serialize";
return result;
},
parseValue(value) {
let result;
// Implement your own behavior here by setting the 'result' variable
result = value || "I am the results of myCustomScalarType.parseValue";
return result;
},
parseLiteral(ast) {
switch (ast.kind) {
// Implement your own behavior here by returning what suits your needs
// depending on ast.kind
}
}
});
const schemaString = `
scalar MyCustomScalar
scalar JSON
type Foo {
aField: MyCustomScalar
bField: JSON
cField: String
}
type Query {
foo: Foo
}
`;
const resolverFunctions = {
Query: {
foo: {
aField: () => {
return 'I am the result of resolverFunctions.Query.foo.aField'
},
bField: () => ({ result: 'of resolverFunctions.Query.foo.bField' }),
cField: () => {
return 'I am the result of resolverFunctions.Query.foo.cField'
}
},
},
};
const mocks = {
Foo: () => ({
// aField: () => mocks.MyCustomScalar(),
// bField: () => ({ result: 'of mocks.foo.bField' }),
cField: () => {
return 'I am the result of mocks.foo.cField'
}
}),
cField: () => {
return 'mocking cField'
},
MyCustomScalar: () => {
return 'mocking MyCustomScalar'
},
JSON: () => {
return { result: 'mocking JSON'}
}
}
const query = `
{
foo {
aField
bField
cField
}
}
`;
const schema = makeExecutableSchema({
typeDefs: schemaString,
resolvers: resolverFunctions
})
addMockFunctionsToSchema({
schema,
mocks
});
// Here we Monkey-patch the schema, as otherwise it will fall back
// to the default serialize which simply returns null.
schema._typeMap.JSON._scalarConfig.serialize = () => {
return { result: 'mocking JSON monkey-patched' }
}
schema._typeMap.MyCustomScalar._scalarConfig.serialize = () => {
return mocks.MyCustomScalar()
}
graphql(schema, query).then((result) => console.log('Got result', JSON.stringify(result, null, 4)));
I and a few others are seeing a similar issue with live data sources (in my case MongoDB/Mongoose). I suspect it is something internal to the graphql-tools makeExecutableSchema and the way it ingests text-based schemas with custom types.
Here's another post on the issue: How to use graphql-type-json package with GraphQl
I haven't tried the suggestion to build the schema in code, so can't confirm whether it works or not.
My current workaround is to stringify the JSON fields (in the connector) when serving them to the client (and parsing on the client side) and vice-versa. A little clunky but I'm not really using GraphQL to query and/or selectively extract the properties within the JSON object. This wouldn't be optimal for large JSON objects I suspect.
If anyone else comes here from Google results, the solution for me was to add the JSON resolver as parameter to the makeExecutableSchema call. It's described here:
https://github.com/apollographql/apollo-test-utils/issues/28#issuecomment-377794825
That made the mocking work for me.

Resources