gqty
gqty copied to clipboard
gqty needs a dynamic mocks solution.
I really wanted to love gqty and use it for all of my projects.
Just writing code as if it was an object and have React Suspense load it is fantastic!
But ultimately I had to switch back to Apollo hooks due to the lack of mocking abilities with gqty.
I got so far as to have a fully dynamic mock system set up with gqty
using @graphql-tools
's mockServer
to create a schema with mocks, and relying on my testing env variable to query the mockServer instead of using the regular queryFetcher in the generated gqty file.
It wasn't easy to set up, but it worked great! It would auto mock anything I wanted using faker with a seed for consistency in tests.
However in some tests you always need to override individual results and that's where gqty
fell apart for me.
Since you don't define query names when using gqty
I just had no way to reasonably identify a query.
My first thought was using types, but that didn't end up being feasible especially if the same type appeared multiple times in my component.
So I thought, I could serialize the query itself and use that as a key in my mockStore
, which was "okay" for simple queries.
But when I started making even minor queries I quickly ended up with this mess:
mockQuery({
query: {
query: {
crm: {
__typename: true,
sitesConnection0: {
__typename: true,
totalCount: true,
__aliasFor: 'sitesConnection',
},
sitesConnection1: {
__typename: true,
totalCount: true,
__aliasFor: 'sitesConnection',
__args: { condition: { active: true } },
},
sites0: {
__typename: true,
id: true,
__aliasFor: 'sites',
__args: { first: 1, condition: { active: true } },
},
},
},
},
result: {
data: {
crm: {
__typename: 'crmQuery',
sitesConnection0: {
__typename: 'SitesConnection',
totalCount: 14413,
},
sitesConnection1: {
__typename: 'SitesConnection',
totalCount: 12081,
},
sites0: [{ __typename: 'Site', id: 1 }],
},
},
},
})
This technically works... but no one wants to write or see that code in your test files just to override a graphql mock.
In other graphQL libraries you would usually assign a name to a query like this:
query SiteCounts {
crm {
sites: sitesConnection {
totalCount
}
activeSites: sitesConnection(condition: { active: true }, first: 1) {
totalCount
nodes {
id
}
}
}
}
and then in e.g. Apollo you can mock it like so:
const mocks = {
SiteCounts: {
crm: {
sitesConnection: {
totalCount: 0,
},
},
},
}
renderWithRouter(
<AutoMockedProvider mocks={mocks}>
<Dashboard />
</AutoMockedProvider>
)
But that method relies on the name I gave the query SiteCounts
I'm honestly not sure what a good solution for gqty would even look like. But hopefully this can start a discussion to find a way to implement a reasonable mocking system using gqty.
If it's any help this is the mockServer I created with gqty that "worked" with the serialized queries, but stringifying the query as the key is not useable.
import { mockServer, addMocksToSchema } from '@graphql-tools/mock'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { graphQlQueryToJson } from 'graphql-query-to-json'
import schemaString from '../schema.graphql?raw'
import mocks from './mocks'
// mockStore
const mockStore = new Map()
type setMockType = { query: any; result: any }
export const mockQuery = ({ query, result }: setMockType) => {
mockStore.set(String(query), result)
}
export const resetMocks = () => {
mockStore.clear()
}
// mockServer
const schema = makeExecutableSchema({ typeDefs: schemaString })
const schemaWithMocks = addMocksToSchema({ schema, mocks })
const server = mockServer(schemaWithMocks, mocks)
const runQuery = (query: string, variables: any) => {
// If there's an override in mockStore, return that.
const queryKey = JSON.stringify(graphQlQueryToJson(query))
if (mockStore.has(queryKey)) {
return mockStore.get(queryKey)
}
return server.query(query, variables)
}
export default { query: runQuery }
@vicary came up with the ideal solution to this problem on Discord.
This would be completely solved by adding operatioName
to the useQuery.
like so:
const query = useQuery({ operationName: "SiteCounts" })
That would make it possible to easily override individual queries based on the operationName
like we can with most other graphql clients 🎉
All credits to @vicary for the idea.
bump
I think the operation name approach is best paired with a mockable query fetcher.
While a built-in solution similar to the Apollo approach is handy, I want to gradully move the codebase into a more modular structure for future plugins support.
I am imagining a mockable fetcher which intercepts the operation name and returns the mocked result, where you put it into the generated client as one of the test fixtures.
Does that makes sense? @MarkLyck