Open spencerolsen opened 5 years ago
My current testing approach involves executing an integration style test against the GraphQL query, and then observing/mocking various pieces of the context as necessary.
I've written a custom graphql-code-generator template that I'm hoping to clean up to be able to open-source to help with this.
It takes "test case" graphql files and turns them into self contained functions that have type completion on the arguments and execute queries against the graphql endpoint.
So given this .graphql
file:
query Test_project($projectId: String!) {
project(id: $projectId) {
id
}
}
This would be an example use in a test case:
test('missing project', () => {
const response = await testProjectQuery({projectId: 'some-missing-project'})
expect(result.body.data).toEqual(null)
expect(result.body.errors).toEqual([
{
extensions: { code: 'INTERNAL_SERVER_ERROR' },
locations: [{ column: 3, line: 2 }],
message: 'INTERNAL_SERVER_ERROR',
path: ['project'],
},
])
})
Here's an abbreviated snippet of what the template/generated code looks like:
export type Test_ProjectQueryVariables = {
projectId: Scalars['String']
}
export type Test_ProjectQuery = { __typename?: 'Query' } & {
project: { __typename?: 'Project' } & Pick<Project, 'id'>
}
const ENDPOINT = '/graphql'
import gql from 'graphql-tag'
import { app as TestApp } from '@packages/api-graphql'
import supertest from 'supertest'
import {
DocumentNode,
OperationDefinitionNode,
print,
GraphQLFormattedError,
} from 'graphql'
type GraphQLTestCaseResult<ResultType> = {
status: number
body: {
data: ResultType | null
errors: GraphQLFormattedError[]
}
}
function makeGraphqlTestCase<ResultType, Variables>(document: DocumentNode) {
return async (
variables: Variables
): Promise<GraphQLTestCaseResult<ResultType>> => {
const operationNode = document.definitions[0] as OperationDefinitionNode
const body = {
query: print(document),
variables,
operationName: operationNode.name ? operationNode.name.value : null,
}
return (
supertest(TestApp)
.post(ENDPOINT)
.send(body)
.set('Accept', 'application/json')
.catch((e) => {
if (e.response) {
console.error(e.response.text)
} else {
console.error(e)
}
throw e
})
.then((res) => {
const result = {
status: res.status,
body: res.body,
}
return result
})
)
}
}
export const Test_ProjectDocument = gql`
query Test_project($projectId: String!) {
project(id: $projectId) {
id
}
}
`
export const testProjectQuery = makeGraphqlTestCase<
Test_ProjectQuery,
Test_ProjectQueryVariables
>(Test_ProjectDocument)
I'm pairing this with other utilities I've written that help me mock/intercept pieces of the request/response more granularly. Though it sounds like the approach of just calling out to your already tested resolve fns would be a good approach as well.
It would be great to have official docs and examples on testing resolvers.
I'm trying to test my Nexus API, and have been looking or hours on ways to do this. We have existing tests from our old schema first API that uses makeExecutableSchema() to test, but I see no way of use makeExecutableSchema() with Nexus. Although it does seem possible, just can't find any documentation on how to actually do it.
We have existing tests from our old schema first API that uses makeExecutableSchema() to test
makeExecutableSchema
just constructs a GraphQLSchema
object.
makeSchema
in Nexus returns the same GraphQLSchema
, so however you were using the schema from graphql-tools
should work the same with Nexus.
Unfortunately, the Schema returned from makeSchema doesn't work I did find that this works:
const testSchema = makeExecutableSchema({
typeDefs: importSchema(path.join(__dirname, '../generated/schema.graphql')),
resolvers: resolvers
});
But it only works because we haven't yet moved the resolvers into the Nexus files, and have them separately, which defeats the point of Nexus as we have to maintain to file structures if we don't move them in, also there's other reason we want to get rid of the separate resolvers.
So the real question I guess is:
Here's one of my tests for reference:
import { addMockFunctionsToSchema } from 'graphql-tools';
import { graphql } from 'graphql';
import { testSchema as schema } from '../schema';
describe('Query Tests: getSomething', () => {
test('getSomething', done => {
const data = {
state: 'CA',
number: 1,
year: 2020,
};
const mocks = {
something: () => ({
state: () => data.state,
number: () => data.number,
year: () => data.year
})
};
addMockFunctionsToSchema({ schema, mocks });
const query = `
query {
getSomething(id: 1) {
state
number
year
}
}
`;
graphql(schema, query).then(result => {
expect(result.data.getSomething).toEqual(data);
done();
});
});
});
Assuming you're testing the server implementation, if you're mocking all the resolvers then it isn't actually testing anything meaningful other than that the execution of graphql-js
functions properly.
All tests I'll typically write are integration level tests, meaning the actual resolvers are executed, and any mocks are dealt with at either the network layer: https://github.com/nock/nock, by stubbing/mocking actual object methods at the context layer, or by pre-seeding a database with expected results.
Since nexus uses Apollo Server you can use Apollo Server testing. See Integration testing. A tutorial can be added that follow Apollo tutorial but uses Nexus schema
A doc exists that recommends best practice with testing a Nexus schema https://nexusjs.org/docs/getting-started/tutorial/chapter-testing-your-api
Can this issue be closed?
@tgriesser does nexus offer any typescript helpers to infer the resolver function types based on field types and args? Currently, the only way I can get my resolvers with type safety is to use them within field definition.
Due to this, all my resolvers are tightened with the schema definition (due to typescript typesafety) and therefore can not be tested separately, without a test graphql server/integration test.
I have raised the same question in this issue: https://github.com/graphql-nexus/nexus/issues/1012
Would that be considered as a pull request? I believe would give more flexibility to Nexus?
What is the recommended method for testing a custom resolver with nexus? Currently the below code is harder for us to test, so we ended up passing the call to our previously existing resolvers that were easier to test.
Our project is nodejs/typescript and as much as possible we try to write classes in order to make testing easier. What are best/recommended practices?