Formatting and Displaying Back-End API's
Intro
In this lesson you'll learn how to format your JSON API responses on your terminal to mimic JSON responses on your Browsers console. This will make it easier for you as a developer to read through your API responses and extrapolate the correct data you need to send back as a response to your React Front-End.
Formatting and Manipulating response data
Currently, when we travel to http://http://127.0.0.1:8000/api/v1/noun/ on our Browser, we can see our API response from the Noun Project displaying on our Terminal. There's a lot of content here! In the browser, it was easier to dig into large data structures, but it's not quite as easy to read large data structures in Python due to how they print in the terminal. Lets use a built-in python module, pprint (pretty print) that'll help us read the responses from the API.
import pprint
# only go 2 levels deep, so we get a general idea of the response without having to look at the whole thing
pp = pprint.PrettyPrinter(indent=2, depth=2)
# within our Noun_project APIView exchange the final print statement for the following
pp.pprint(responseJSON)
Refresh the browser to send a
GETrequest to our Django Server once more. Now we can visualize our response data and work to return the icon_url in our get methodsResponse
return Response(responseJSON['icon']['icon_url'])
Refresh the browser one more time and you'll see the DRF template displaying an image url path that is being returned by our get methods Response.
Displaying Icon URL
We have the information we need from the Noun Project API now, but we haven't displayed this information to our users. Let's add a
typefield to our Pokemon with some validation adn a default value. Let's make our Front-end application render an Icon for each specific pokemon type in thePokemon.jsxpage.
Adjusting our Django Back-End
# pokemon_app.validators
def validate_type(value):
allowed_types = ['rock', "normal", 'bug', 'ghost', 'steel', 'fire', 'water', 'grass', 'electric', 'psychic', 'ice', 'dragon', 'dark', 'fairy', 'unknown', 'shadow']
if value.lower() not in allowed_types:
raise ValidationError(f"Invalid type: {value}. Please choose from {', '.join(allowed_types)}.")
# pokemon_app.models
class Pokemon(models.Model):
#...
type = models.CharField(default="normal", validators=[validate_type])
#pokemon_app.serializers
class PokemonSerializer(ModelSerializer):
# ...
class Meta:
# ...
fields = ['id', 'name', 'level', 'moves', "type"]
Make sure to migrate our changes to our database with the following commands
# TERMINAL
python manage.py makemigrations
python manage.py migrate
The last thing we need to do is update our
urland CBV to accept astrparameter oftypesthat we can utilize with interpolated string to look for a specificnoun icon_url.
#api_app.urls
from django.urls import path
from .views import Noun_Project
urlpatterns = [
path('<str:types>/', Noun_Project.as_view(), name="noun_project"),
]
# api_app.views
class Noun_Project(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, types):
auth = OAuth1("your-api-key", "your-api-secret")
endpoint = f"http://api.thenounproject.com/icon/{types}"
response = requests.get(endpoint, auth=auth)
responseJSON = response.json()
pp.pprint(responseJSON)
return Response(responseJSON['icon']['icon_url'])
Testing Requests
- Testing works a bit different with API's since we don't want to actually make the API call every time we run our test. So instead unit tests have the ability to mock API calls.
# tests/test_views.py
from unittest.mock import patch
from rest_framework.test import APIClient
# ...
class NounProjectTest(TestCase):
def setUp(self):
self.client = APIClient()
-
The
importstatements include the necessary modules for the test case:jsonfor JSON-related operations,patchfromunittest.mockto mock therequests.getfunction,TestCasefromdjango.testfor creating test cases,APIClientfromrest_framework.testto simulate API requests, andreversefromdjango.urlsfor resolving URL paths. -
The
NounProjectTestclass is a subclass of Django'sTestCase, indicating that this is a test case for the Noun Project functionality. -
The
setUpmethod is a special method that runs before each test method in the test case. Here, we create an instance of theAPIClientto make API requests.
@patch('requests.get')
def test_pokeball_img_api_view(self, mock_get):
ball = 'pokeball'
preview_url = "https://example.com/image.png"
mock_response = type('MockResponse', (), {'json': lambda self: {'icon': {'icon_url': preview_url}}})
mock_get.return_value = mock_response()
response = self.client.get(reverse('noun_project', args=[ball]))
-
The
@patch('requests.get')decorator patches therequests.getfunction, allowing us to intercept and control the API call made byrequests.getduring the test. This prevents the actual API request from being made and replaces it with a mocked response. -
In the
test_pokeball_img_api_viewmethod, we define the test for the API view that retrieves the image URL for a given ball. Here, we're assuming that the view is registered with the namenoun_projectin the Django URL configuration. -
The
ballvariable holds the ball name to be passed as an argument to the view. -
The
preview_urlvariable represents the URL of the image that we expect to receive in the response. -
The
mock_responseline creates a mock response object with ajsonmethod that returns a dictionary with the expected JSON structure of the response. In this case, it simulates the structure returned by the API. -
mock_get.return_value = mock_response()assigns the mock response object to the patchedrequests.getfunction, making it return the mock response instead of performing an actual API call. -
response = self.client.get(reverse('noun_project', args=[ball]))makes a GET request to thenoun_projectURL, passing theballargument as a URL parameter. This triggers the view and allows us to test its behavior.
with self.subTest():
self.assertEqual(response.status_code, 200)
self.assertEquals(json.loads(response.content), preview_url)
-
with self.subTest():creates a sub-test block, allowing multiple assertions within a single test method. This helps isolate and identify individual assertions if one of them fails. -
self.assertEqual(response.status_code, 200)asserts that the response status code is200, indicating a successful API request. -
self.assertEquals(json.loads(response.content), preview_url)asserts that the JSON response content, once loaded, is equal to the expectedpreview_url.
By utilizing the
@patchdecorator andunittest.mocklibrary, we can intercept and control the behavior of therequests.getfunction during testing. This allows us to simulate different API responses and test the behavior of our Django views without actually making real API calls.Before running the test suite make sure to fix your answers and update fixtures since we added a type field to our Pokemon model. If you've been following along you could just use the fixtures and answers provided in the example