Caco
Caco

Reputation: 1654

Testing function that interacts with Django Rest Framework API using python mock library

I want to unit test a module that interacts with an API built with Django Rest Framework. To do that I'm using python mock library. As I'm not experienced using mocks to test interaction with an API, I'm having problems.

The function that sends an object to an API endpoint is this (in short):

def send_experiment_to_portal(experiment: Experiment):
    rest = RestApiClient()

    if not rest.active:
        return None

    # general params
    params = {"nes_id": str(experiment.id),
              "title": experiment.title,
              "description": experiment.description,
              "data_acquisition_done":
                  str(experiment.data_acquisition_is_concluded),
              "project_url": experiment.source_code_url,
              "ethics_committee_url": experiment.ethics_committee_project_url
              }

    action_keys = ['experiments', 'create']

    portal_experiment = rest.client.action(
        rest.schema, action_keys, params=params,
    )

return portal_experiment

The RestAPIClient is the class that authenticates to the API:

class RestApiClient(object):
    client = None
    schema = None
    active = False

    def __init__(self):
        auth = coreapi.auth.BasicAuthentication(
            username=settings.PORTAL_API['USER'],
            password=settings.PORTAL_API['PASSWORD']
        )
        self.client = coreapi.Client(auth=auth)

        try:
            url = settings.PORTAL_API['URL'] + \
                  (
                      ':' + settings.PORTAL_API['PORT'] if
                      settings.PORTAL_API['PORT'] else ''
                  ) \
                  + '/api/schema/'

            self.schema = self.client.get(url)
            self.active = True
        except:
            self.active = False

The RestApiClient class is using the coreapi client module to connect to the API.

So, at the end, I'm starting from test if the params argument in rest.client.action has the keys/values that are foreseen by the API using python mock library.

I'm starting writing the initial test like:

class PortalAPITest(TestCase):

    @patch('experiment.portal.RestApiClient')
    def test_send_experiment_to_portal(self, mockRestApiClientClass):
        research_project = ObjectsFactory.create_research_project()
        experiment = ObjectsFactory.create_experiment(research_project)

        result = send_experiment_to_portal(experiment)

So when I debug the test, looking inside the send_experiment_to_portal function rest.client.action.call_args has the value:

call(<MagicMock name='RestApiClient().schema' id='139922756141240'>, ['expreiments', 'create'], params={'description': 'Descricao do Experimento-Update', 'ethics_committee_url': None, 'project_url': None, 'title': 'Experimento-Update', 'nes_id': '37', 'data_acquisition_done': 'False'})

But mockRestApiClientClass.client.action.call_args has None. I suspect that I'm making wrong. I should mock rest.client.action and not the RestApiClient class.

Trying to use result value does not work too.

The value of result is

<MagicMock name='RestApiClient().client.action()' id='139821809672144'>

and result.call_args is None too.

But how to do that? How to get the mocking rest instance outside send_experiment_to_portal function?

Upvotes: 1

Views: 875

Answers (1)

Caco
Caco

Reputation: 1654

After struggling for a couple of days I could find a solution.

Mocking RestApiClient returns the mock of the class not its value, that is the value of rest variable. We get the rest value calling return_value from the mock object.

So, we have to call mockRestApiClientClass.return_value instead of mockRestApiClientClass.

By doing that we have access to the rest methods and attributes called from inside send_experiment_end_message_to_portal, and then mockRestApiClientClass.return_value.client.action.call_args will have the same content of the observed rest content inside send_experiment_end_message_to_portal function context.

Upvotes: 1

Related Questions