Block internet connection for testing purposes
• • ☕️ 2 min readI wanted to make sure my tests will never ever contact any external API. It’d be best if an error was raised so I knew I forgot to add a mock.
To my knowledge, Django does not provide such functionality out of the box but it’s easy to plug in
a library that already solves this problem - httpretty
. All its activation methods (and there are three)
accept allow_net_connect
parameter, which set to False
will raise a httpretty.errors.UnmockedError
if your test
tries to contact the outside world.
httpretty.activate
- is a handy decorator. It works fine but for some reason, when you set
allow_net_connect=True
in Django, the test runner can’t find the test. Even if this
issue was resolved, it won’t be the prettiest solution as
basically you’d have to set it per test/test class.
httpretty.enable
- to my understanding this method
enables the “global mock” but in the official docs you can find this warning
after calling this method the original socket is replaced with httpretty.core.fakesock. Make sure to call disable() after done with your tests or use the httpretty.enabled as decorator or context-manager
which points to an even better solution.
httpretty.enabled
- context-manager for enabling
HTTPretty.
In Django it’s simple to extend the functionality of a test runner. If you want to disable network communication
for the test suite and just that, use httpretty.enabled
in your test runner.
# common/test_runner.py
import httpretty
from django.test.runner import DiscoverRunner
class CustomTestRunner(DiscoverRunner):
def run_tests(self, *args, **kwargs):
with httpretty.enabled(allow_net_connect=False):
return super().run_tests(*args, **kwargs)
and in your test settings
TEST_RUNNER = "common.test_runner.CustomTestRunner"
and all the external API calls will have to be mocked. Otherwise, an error will be raised.
import requests
from django.test import TestCase
class DjangoAPIMockExampleTestCase(TestCase):
def test_third_party_api(self):
top_story_ids = requests.get("https://hacker-news.firebaseio.com/v0/topstories.json").json()
httpretty.errors.UnmockedError: Failed to handle network request.
Intercepted unknown GET request https://hacker-news.firebaseio.com/v0/topstories.json
So all that is left is to mock the forgotten API call.
import json
import httpretty
import requests
from django.test import TestCase
from rest_framework import status
class DjangoAPIMockExampleTestCase(TestCase):
def test_third_party_api(self):
expected_ids = list(range(1, 501))
httpretty.register_uri(
httpretty.GET,
'https://hacker-news.firebaseio.com/v0/topstories.json',
body=json.dumps(expected_ids)
)
response = requests.get("https://hacker-news.firebaseio.com/v0/topstories.json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), expected_ids)
Or, if you are using pytest
, this fixture should work.
@pytest.fixture
def disable_external_api_calls():
httpretty.enable()
yield
httpretty.disable()