Graphene: How can I test output of graphene when error is thrown?

Created on 16 Mar 2019  ·  7Comments  ·  Source: graphql-python/graphene

Resolver:

def resolve_entity(self, info, **kwargs):
        id = kwargs.get('id')

        if id is None:
            raise GraphQLError('ID not provided')

        try:
            return Entity.objects.get(pk=id)
        except Entity.DoesNotExist:
            raise GraphQLError('Entity not found')

Entity:

    def test_single_athlete_no_id(self):
        athlete = Entity.objects.create(name='John Doe', weight=23.45,
                                         height=180)
        try:
            self.client.execute(
                '{ entity { id, name, weight, height } }')
        except:
            print('test')

I have been trying different ways (assertRaises, using try/except) but nothing is raised when there is definitely a stack trace like the following:

..An error occurred while resolving field Query.entity
Traceback:
...
raise GraphQLError('ID not provided')
graphql.error.base.GraphQLError: ID not provided
Traceback:
...
graphql.error.located_error.GraphQLLocatedError: ID not provided

Why am I not able to catch these exceptions? Am I not supposed to use exceptions for error handling?

Most helpful comment

@iraj-jelo - that's an interesting point, but maybe a separate discussion. One of the neat things about the GraphQL spec is that it allows for a partial response to a query where only the successfully resolved fields are returned!

There are a couple of things about exception handling in graphene that can be surprising (like how exceptions are automatically caught for resolvers as I mentioned above). I do wonder though why the server might encounter a missing required argument when submitting a query from your frontend - it may make sense for the frontend to ensure that the parameter is always passed through before sending the request.

Feel free to submit an issue with more details on this to chat through the concern in more depth.

All 7 comments

I think providing a way to customizing error messages such "required=True"s in fields is required and urgent for developers.

the uncustomized error messages which we are getting very often are obscure and unclear and dirty to show final user in front-end UI (forms) .

@GasimGasimzada - here's a short example of an assertion on errors and data in the response from a query execution against a graphene schema.

All exceptions that occur during the execution of each field are caught and collected in the result's errors attribute. You normally won't see any errors bubbling up past a call to execute.

query_string = '''
query getAuthorNode($id: ID!) {
            author: node(id: $id) {
                ... on AuthorType {
                    id
                    firstName
                    lastName
                    twitterAccount
                }
            }
        }
'''
schema = graphene.Schema(query=MyRootQuery, types=(AuthorType,))
result = schema.execute(query_string, context=self.request, variables=variables)

self.assertEqual(result.errors, None)
self.assertDictEqual(result.data['author'], {
    'id': to_global_id(AuthorType, 3),
    'firstName': 'Buddy',
    'lastName': 'Holly',
    'twitterAccount': '@buddy',
})

I have actually found the issue here. When I am calling execute and raising exception from my resolver, the error is immediately printed to the console. So the above code prints a stack trace during testing. I ended up manually performing an HTTP request to the graphql endpoint and testing the results as it was the only way it worked for me.

@GasimGasimzada - exceptions raised in resolvers do seem to be logged out to the console with stack trace, but you can make an assertion without HTTP request by making an assertion against the errors attribute of ExecutionResult as noted in my previous response.

You can also simplify your resolver by making the id field mandatory in the schema definition for this field.

    entity = graphene.Field(Entity, args={'id': graphene.Int(required=True)})
    def resolve_entity(self, info, id):
        try:
            return Entity.objects.get(pk=id)
        except Entity.DoesNotExist:
            raise GraphQLError('Entity not found')

@iraj-jelo - that's an interesting point, but maybe a separate discussion. One of the neat things about the GraphQL spec is that it allows for a partial response to a query where only the successfully resolved fields are returned!

There are a couple of things about exception handling in graphene that can be surprising (like how exceptions are automatically caught for resolvers as I mentioned above). I do wonder though why the server might encounter a missing required argument when submitting a query from your frontend - it may make sense for the frontend to ensure that the parameter is always passed through before sending the request.

Feel free to submit an issue with more details on this to chat through the concern in more depth.

@dvndrsn My issue is that I am trying to test the error cases. And I want my error cases to catch the error and “pass” the test. Instead they are throwing errors. I think it would be logical to use logger for errors instead of printing them to the console.

@GasimGasimzada GraphQL, by definition catches all exceptions and puts errors in the errors part of the response.
If you're testing the execution of a query (self.client.execute(... query ...)) you should get the result and and verify it has an errors part that matches what you expect.

An easier way would be to test the resolver specifically - call resolve_entity directly and not via the GraphQL execution layer, and test it like you would any other Python function.

Was this page helpful?
0 / 5 - 0 ratings