Reputation: 1654
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
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