How To Test External Rest APIs?
Now that we’ve defined the Source Code let’s dive into the core idea. Python REST API unit testing for External APIs.
Functionality Test
Does your code do what it’s supposed to do? Does it produce the correct data and or result?
Test List Breeds (GET)
The below Code tests the list_breeds()
method by passing a custom payload and asserting against an expected response
test_dog_api_core_basic.py
2. Test Search Breeds and Create Vote (GET, POST)
We can repeat this type of Unit Test to cover the other 2 methods — search_breeds()
and create_vote()
.
test_dog_api_core_basic.py
Authentication Test
What happens if our Environment Variable (& API Key) is lost?
Our API will not work. So we need to be notified and look through the logs.
In this case, we override the initialised class and headers with a temporary one, containing a FAKE Key string. Obviously, this is not going to work and as expected, the API returns a 401 status code.
Error Handling Test
Can your code handle errors from the server?
For example — payload errors, API limit errors or maybe the pagination times out?
Schema Test
One of the biggest cases of API breakage is schema changes.
Testing for schema changes, backwards compatibility and notifying you ASAP is extremely important.
Speed and RunTime Tests
What’s the average response time or latency of the 3rd party API?
Has it suddenly gone from 100ms to 3000ms due to a new release or issues with server capacity?
Should You Call The Real API?
Perhaps you have a monitoring environment that runs tests every hour to make sure the API is working as expected and the service is marked as healthy
.
This would mean you’d hit your API limit pretty easily, costing you more dollars and wasting precious computing resources.
Running Unit Tests as part of a CI/CD Pipeline, especially when releasing to Production is a good idea.
But for local or high-volume repeated environment testing, mocking is a good strategy.
Mock The API Response Instead
How can we simulate the API response?
We use Mocks and Patches.
Mocking or Patching is the practice of telling the Unit Testing framework to return a specific value for a variable, function or class object instantiation.
def test_mock_list_breeds(mocker, dog_api) -> None:
mocked_call_api_value = [
{'weight': {'imperial': '44 - 66', 'metric': '20 - 30'},
'height': {'imperial': '30', 'metric': '76'}, 'id': 3,
'name': 'African Hunting Dog',
'bred_for': 'A wild pack animal', 'life_span': '11 years',
'temperament': 'Wild, Hardworking, Dutiful', 'origin': '',
'reference_image_id': 'rkiByec47',
'image': {'id': 'rkiByec47', 'width': 500, 'height': 335,
'url': 'https://cdn2.thedogapi.com/images/rkiByec47.jpg'}}]
mocker.patch('dog_api.core.DogAPI.call_api',
return_value=mocked_call_api_value)
actual_mock_response = dog_api.list_breeds(
query_dict={"attach_breed": 1, "page": 2,
"limit": 1})
assert mocked_call_api_value == actual_mock_response
def test_mock_search_breeds(mocker, dog_api) -> None:
mocked_call_api_value = [
{'weight': {'imperial': '13 - 17', 'metric': '6 - 8'},
'height': {'imperial': '13 - 14', 'metric': '33 - 36'},
'id': 139,
'name': 'Jack Russell Terrier', 'bred_for': 'Fox hunting',
'breed_group': 'Terrier',
'life_span': '12 - 14 years'}]
mocker.patch('dog_api.core.DogAPI.call_api',
return_value=mocked_call_api_value)
actual_mock_response = dog_api.search_breeds(
query_str="Jack Russell Terrier")
assert mocked_call_api_value == actual_mock_response
def test_mock_create_vote(mocker, dog_api) -> None:
mocked_call_api_value = {
'message': 'SUCCESS', 'id': 129143, 'image_id': 'asf2',
'value': 1, 'country_code': 'AR'}
mocker.patch('dog_api.core.DogAPI.call_api',
return_value=mocked_call_api_value)
actual_mock_response = dog_api.create_vote(
{"image_id": "asf2", "value": 1})
assert mocked_call_api_value == actual_mock_response
In this example, we’ll use the pytest-mock library, which is a wrapper around the patching API provided by the mock package.
The call_api()
function has some logical conditions to execute a GET, PUT
request. But we don’t really care about the working of this method.
So we “mock” the response of call_api()
i.e. produce a fake/mocked response.