Skip to main content

HTTP server for testing environments

Project description

https://github.com/lexsca/spoof/actions/workflows/checks.yml/badge.svg https://img.shields.io/pypi/v/spoof.svg https://img.shields.io/pypi/pyversions/spoof.svg https://img.shields.io/github/license/lexsca/spoof.svg https://img.shields.io/badge/code%20style-black-000000.svg

Spoof is a simple HTTP server for test environments.

>>> import requests
... import spoof
...
... with spoof.HTTPServer() as httpd:
...     httpd.queueResponse([200, [], "This is Spoof 👻👋"])
...     requests.get(httpd.url).text
...     httpd.requests
...
'This is Spoof 👻👋'
[SpoofRequestEnv(method='GET', uri='/', protocol='HTTP/1.1', serverName='localhost', serverPort=62775, headers=<http.client.HTTPMessage object at 0x10d8a8f50>, path='/', queryString=None, content=None, contentType=None, contentEncoding=None, contentLength=0)]

Test interface for HTTP

Spoof lets you easily create HTTP servers listening on real network sockets. Designed for test environments, what responses to return can be configured while an HTTP server is running, and requests can be inspected live or after a response is sent.

Unlike a traditional HTTP server, where specific methods and paths are configured in advance, Spoof accepts and captures all requests, sending whatever responses are queued, or a default response if the queue is empty.

Why would I want this?

Spoof is all about enabling test-driven development (and refactoring) of HTTP client code. Have you ever felt icky patching a client library to write tests? Ever been burned by this? Ever wanted to refactor a client library, but had no way to prove functionality apart from doing live integration testing? If you answered yes to any of the above, Spoof is for you.

Compatibility

Spoof is tested on Python 3.10 to 3.14, leverages the http.server module included in the standard library, and has no external dependencies. It may work on older versions of Python, but this is not supported.

Multiple Spoof HTTP servers can be run concurrently, and by default, the port number is the next available unused port. With OpenSSL installed, Spoof can also provide an SSL/TLS HTTP server. IPv6 is fully supported.

Spoof HTTP servers run in a single background thread, so request and response order should be predictable. Tests should be able to use the same fixtures, in the same order, and get the same results.

SpoofRequestEnv instances

Spoof captures each request as a namedtuple with the following properties:

Property

Description

content

bytes object of request content

contentEncoding

Value of Content-Encoding header, if present

contentLength

Value of Content-Length header, if present

contentType

Value of Content-Type header, if present

headers

http.client.HTTPMessage object of headers

method

Request method (e.g. GET, POST, HEAD)

path

Decoded URI path, without query string

protocol

Protocol version (e.g. HTTP/1.0)

queryString

Anything in URI after ?

serverName

Host name of HTTP server

serverPort

Port number of HTTP server

uri

Raw URI path and query string, if present

Queued responses

Queue multiple responses, verify content, and request paths:

import requests
import spoof

with spoof.HTTPServer() as httpd:
    responses = [
        [200, [("Content-Type", "application/json")], '{"id": 1111}'],
        [200, [("Content-Type", "application/json")], '{"id": 2222}'],
    ]
    httpd.queueResponse(*responses)
    httpd.defaultResponse = [404, [], "Not found"]

    assert requests.get(httpd.url + "/path").json() == {"id": 1111}
    assert requests.get(httpd.url + "/alt/path").json() == {"id": 2222}
    assert requests.get(httpd.url + "/oops").status_code == 404
    assert [r.path for r in httpd.requests] == ["/path", "/alt/path", "/oops"]

Callback response

Set a callback as the default response (callbacks can also be queued):

import requests
import spoof

with spoof.HTTPServer() as httpd:
    httpd.defaultResponse = lambda request: [200, [], request.path]

    assert requests.get(httpd.url + "/alt").content == b"/alt"

SSL/TLS Mode

Test queued response with a self-signed SSL/TLS certificate:

import requests
import spoof

with spoof.SelfSignedSSLContext() as selfSigned:
    with spoof.HTTPServer(sslContext=selfSigned.sslContext) as httpd:
        httpd.queueResponse([200, [], "No self-signed cert warning!"])
        response = requests.get(httpd.url + "/path",
                                verify=selfSigned.certFile)

        assert httpd.requests[-1].method == "GET"
        assert httpd.requests[-1].path == "/path"
        assert response.content == b"No self-signed cert warning!"

Proxy Mode

Spoof also supports proxying HTTP requests by setting the upstream attribute to another Spoof instance:

import requests
import spoof

with spoof.SelfSignedSSLContext(commonName="example.spoof") as ssl:
    with spoof.HTTPServer(sslContext=ssl.sslContext) as proxy:
        with spoof.HTTPServer(sslContext=ssl.sslContext) as upstream:
            proxy.upstream = upstream
            proxy.defaultResponse = [200, [("X-Spoof-Proxy", "True")], ""]
            upstream.defaultResponse = [200, [], "I'm here!"]
            response = requests.get(
                "https://example.spoof/ayt",
                proxies={"https": proxy.url},
                verify=ssl.certFile
            )
            assert proxy.requests[0].method == "CONNECT"
            assert proxy.requests[0].path == "example.spoof:443"
            assert upstream.requests[0].method == "GET"
            assert upstream.requests[0].path == "/ayt"
            assert response.content == b"I'm here!"

Using IPv6

Setting the host attribute to an IPv6 address will work as expected. There is also an IPv6-only spoof.HTTPServer6 class that can be used if needed.

>>> import requests
... import spoof
...
... with spoof.HTTPServer(host="::1") as httpd:
...     httpd.queueResponse([200, [], "This is Spoof on IPv6 👀"])
...     requests.get(httpd.url).text
...     httpd.url
...
'This is Spoof on IPv6 👀'
'http://[::1]:51324'

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spoof-2.0.5.tar.gz (22.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spoof-2.0.5-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file spoof-2.0.5.tar.gz.

File metadata

  • Download URL: spoof-2.0.5.tar.gz
  • Upload date:
  • Size: 22.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spoof-2.0.5.tar.gz
Algorithm Hash digest
SHA256 86f3c9d3b950c23273b87c6d8c1ef9c3b52b5ec5cb4f7ff5ed010bd205a4a831
MD5 328a87ea2530956e41a0cd41c71d030a
BLAKE2b-256 12f704933a9d1195925c816fa79ab4b219320849e53614420591531b074cd84e

See more details on using hashes here.

Provenance

The following attestation bundles were made for spoof-2.0.5.tar.gz:

Publisher: release.yml on lexsca/spoof

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spoof-2.0.5-py3-none-any.whl.

File metadata

  • Download URL: spoof-2.0.5-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spoof-2.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 6b91d697cfbb8f11f852f0ec1624d7cd23e1974470b5ee6d8f4644813716f077
MD5 dea8511ba273e239e34d313bfe91dd92
BLAKE2b-256 e8c0085f2042a38000e1c7f9aae45bc893014b24fc27205aec74dcf5d6dee1ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for spoof-2.0.5-py3-none-any.whl:

Publisher: release.yml on lexsca/spoof

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page