Tom Wojcik personal blog

Block internet connection for testing purposes

☕️ 2 min read

I 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()