Mocking API request tests for Wagtail… with KanyeRest

from home.api_content import KanyeRest
from wagtail.core.models import Page


class HomePage(Page):
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context["kanye_quote"] = KanyeRest().get_data()
return context
Homepage example showing a quote… I know, Kanye is a bit… creative.
Example of data from api.kanye.rest
import json
import logging

import requests
from django.conf import settings


logger = logging.getLogger(__name__)


class KanyeParse:
def __init__(self, data):
self.data = data

# A very simple parser
def get_parsed_data(self):
parsed_data = json.loads(self.data)
return parsed_data["quote"]


class KanyeRest:
def __init__(self):
self.data = None
self.url = "https://api.kanye.rest/"

def get_url(self):
return self.url

def fetch_data(self):
try:
response = requests.get(url=self.url)
self.data = response.content
except requests.exceptions.Timeout:
logger.exception(f"Timeout occurred")
except Exception:
logger.exception(f"Error occurred")
return self.data

def get_data(self):
""" If there is data, parse it, otherwise return an empty list """
self.data = self.fetch_data()
if self.data:
self.data = KanyeParse(self.data).get_parsed_data()
return self.data
else:
return []
  • How does the code handle a Timeout?
  • Does the data go through the parser as expected
  • If there are Exceptions, will the homepage still render?

Testing the API is up

Here, I’m setting up some values to test, seen as though I don’t want to use the live API because data changes with each request. Then a quick test_fetch to check the live API is responding.

class TestKanye(TestCase):
def setUp(self):
self.expected_api_data = {"quote": "All you have to be is yourself"}
self.expected_parsed_data = "I want the world to be better! All I want is positive! All I want is dopeness!"
self.default_data = [{"quote": "This is me, not Kayne"}]

def test_fetch(self):
url = KanyeRest().get_url()
response = requests.get(url, timeout=5)
self.assertEqual(response.status_code, 200)

Testing a fetch with mocked data

Using mock.patch can replace methods and classes with ones you define specifically for tests. Here I’m saying “Don’t use the fetch_data method from my KanyeRest() class, instead use mock_fetched_data”

# Data changes on each request so mock it
@mock.patch("home.api_content.KanyeRest.fetch_data", side_effect=mocked_fetch_data)
def test_fetch_with_example_data(self, mocked_fetch_data):
data = KanyeRest().get_data()
lf.assertEqual(data, self.expected_parsed_data)
def mocked_fetch_data():
data = json.dumps(
{
"quote": "I want the world to be better! All I want is positive! All I want is dopeness!"
}
)
return data

Testing the default data is used with a Timeout

Here with mock.patch, I’m mocking the get request used by requests.get. Doing this allows me to define a ‘side_effect’, and that can be a Timeout exception.

@mock.patch("home.api_content.requests.get")
def test_data_if_timeout(self, mock_get):
""" If a timeout is caught we should be getting the default data"""
mock_get.side_effect = Timeout
data = KanyeRest().get_data()
self.assertEqual(data, self.default_data)

But does the page render with a Timeout?

Yep, this test is similar to the test above, we just check the rendering of the page:

@mock.patch("home.api_content.requests.get")
def test_page_renders_with_timeout(self, mock_get):
""" If there is a timeout for the request when the page loads,
ensure the page still renders with the default data"""
home_page = HomePage.objects.first()
mock_get.side_effect = Timeout
response = self.client.get("/")
self.assertTemplateUsed(response, "home/home_page.html")
self.assertEqual(response.render().status_code, 200)

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store