Mocking response for GraphQL query resolver from ServiceObject to prevent API calls in the UnitTests

Question:

Let’s assume that I have the following service object:

class Foo(object):
    def bar(self):
        return ['foo', 'bar']

And this is the schema:

import Foo

class Query(graphene.ObjectType):
    bar = graphene.List(lambda: graphene.String)

    def resolve_bar(self, info):
        return Foo().bar()

I am trying to test if the GraphQL Schema is correctly calling the method bar in its query resolver. So, we have this piece of code in the tests:

from MySchema import Query

class TestFooBar(unittest.TestCase):
    @patch('Foo')
    def test_bar(self, mock_foo):
        mock_foo.return_value.bar.return_value = ['baz', 'qux']

        my_schema = graphene.Schema(query=Query)
        client = Client(self.my_schema)

        query = '''
            query {
                bar()
            }
        '''
        executed = self.client.execute(query)

       #some attributes that I want to assert
       assertTrue(mock_foo.called) # returns False

Why I am using mocks?

In the original ServiceObject class it makes some API calls to another service, which is already tested in a isolated way. And in this case, I only want to test if the GraphQL query bar is calling a method which will return its supposed object.

The problem

When I mock the response that the service object returns as in the above code, and make the Graphene Client test runs the query, it gives me the ‘non-mocked’ response. In other words, it is actually calling the original method provided by the service object class and making the API calls, which shouldn’t be performed. However, when I instantiate and run the ServiceObject class itself, it is correctly mocked and returns the ['baz', 'qux'] array, not going through the API calls.

Do someone have an ideia of what I am doing wrong?

Or GraphQL Client responses shouldn’t be mocked at all? Is there any approach that I can use instead of mocking it?

I already looked all over internet to see how people do that, but I couldn’t manage to find any possible solutions.

Asked By: Lucas Charles

||

Answers:

So, the problem is:

My patch is wrong. I should patch the callsite, not the definition site. In this case, it will be: @patch('MySchema.Foo') to accomplish the mocking of the callsite.

To prove that

from MySchema import Query

class TestFooBar(unittest.TestCase):
    @patch('MySchema.Foo')
    def test_bar(self, mock_foo):
        mock_foo.return_value.bar.return_value = ['baz', 'qux']

        my_schema = graphene.Schema(query=Query)
        client = Client(self.my_schema)

        query = '''
            query {
                bar()
            }
        '''
        executed = self.client.execute(query)

        assertTrue(mock_foo().bar.called) # now returns True

Thanks to jkimbo who came on the rescue when I asked in the Graphene-Python Github repository.

Answered By: Lucas Charles

I know that this question is about Graphene, but I had the same problem (mocking graphql resolver) with Ariadne and write this solution for others like me who have no access to enough documents about graphql unit tests with ariadne.

As said in the accepted answer, mocking resolver is not enough and graphql still calls the not mocked resolver. So we must change schema to call this new function instead of main resolver. And -in my case- change django url to use new executable schema

from .urls import urlpatterns
with patch('path.to.my_resolver') as mocked_resolver:
    query = QueryType()
    query.set_field(mocked_field, mocked_resolver)
    mocked_resolver.return_value = mocked_return_value
    my_schema = make_executable_schema(my_base_schema, query, snake_case_fallback_resolvers)
    urlpatterns.clear() # remove main urls
    urlpatterns.append(path('graphql/', GraphQLView.as_view(schema=my_schema), name='graphql'))

Note: snake_case_fallback_resolvers is because of handling convert_kwargs_to_snake_case decorator

Answered By: it_nazanin