httptest


Namehttptest JSON
Version 2.1.1 PyPI version JSON
download
home_pagehttps://github.com/pdxjohnny/httptest
SummaryAdd unit tests to your http client
upload_time2024-01-03 19:26:19
maintainerJohn Andersen
docs_urlNone
authorJohn Andersen
requires_python
licenseMIT
keywords unittesting testing http api test
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # httptest

[![Tests](https://github.com/pdxjohnny/httptest/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/pdxjohnny/httptest/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/pdxjohnny/httptest/branch/master/graph/badge.svg)](https://codecov.io/gh/pdxjohnny/httptest)

HTTP testing inspired by golang's httptest package. Supports wrapping asyncio
coroutine functions (`async def`).

## Usage

### Context Manager

```python
import unittest
import urllib.request

import httptest

class TestHTTPServer(httptest.Handler):

    def do_GET(self):
        contents = "what up".encode()
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.send_header("Content-length", len(contents))
        self.end_headers()
        self.wfile.write(contents)

def main():
    with httptest.Server(TestHTTPServer) as ts:
        with urllib.request.urlopen(ts.url()) as f:
            assert f.read().decode('utf-8') == "what up"

if __name__ == '__main__':
    main()
```

### Simple HTTP Server Handler

```python
import unittest
import urllib.request

import httptest

class TestHTTPServer(httptest.Handler):

    def do_GET(self):
        contents = "what up".encode()
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.send_header("Content-length", len(contents))
        self.end_headers()
        self.wfile.write(contents)

class TestHTTPTestMethods(unittest.TestCase):

    @httptest.Server(TestHTTPServer)
    def test_call_response(self, ts=httptest.NoServer()):
        with urllib.request.urlopen(ts.url()) as f:
            self.assertEqual(f.read().decode('utf-8'), "what up")

if __name__ == '__main__':
    unittest.main()
```

### Serve Files

```python
import pathlib
import unittest
import http.server
import urllib.request

import httptest

FILE_PATH = pathlib.Path(__file__)

class TestHTTPTestMethods(unittest.TestCase):

    @httptest.Server(
        lambda *args: http.server.SimpleHTTPRequestHandler(
            *args, directory=FILE_PATH.parent
        )
    )
    def test_call_response(self, ts=httptest.NoServer()):
        with urllib.request.urlopen(ts.url() + FILE_PATH.name) as f:
            self.assertEqual(f.read().decode('utf-8'), FILE_PATH.read_text())

if __name__ == '__main__':
    unittest.main()
```

### Asyncio Support

Asyncio support for the unittest package hasn't yet landed in Python.
[python/issue32972](https://bugs.python.org/issue32972).
It should land in 3.8, check it out
[here](https://github.com/python/cpython/pull/13386).

If you want a quick way to add `asyncio` test cases you can import the helper
from [intel/dffml](https://github.com/intel/dffml).

```python
import sys
import unittest
import urllib.request
if sys.version_info.minor == 3 \
        and sys.version_info.minor <= 7:
    from dffml.util.asynctestcase import AsyncTestCase
else:
    # In Python 3.8
    from unittest import IsolatedAsyncioTestCase as AsyncTestCase

import httptest

class TestHTTPServer(httptest.Handler):

    def do_GET(self):
        contents = "what up".encode()
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.send_header("Content-length", len(contents))
        self.end_headers()
        self.wfile.write(contents)

class TestHTTPTestMethods(AsyncTestCase):

    @httptest.Server(TestHTTPServer)
    async def test_call_response(self, ts=httptest.NoServer()):
        with urllib.request.urlopen(ts.url()) as f:
            self.assertEqual(f.read().decode('utf-8'), "what up")

if __name__ == '__main__':
    unittest.main()
```

In your project's `setup.py`, add `dffml` in `tests_require`.

```python
setup(
    name='your_package',
    ...
    tests_require=[
        'httptest>=0.1.0',
        'dffml>=0.4.0.post0'
    ]
)
```

## Auto Install

If you're making a python package, you'll want to add `httptest` to your
`setup.py` file's `tests_require` section.

This way, when your run `python setup.py test` setuptools will install
`httptest` for you in a package local directory, if it's not already installed.

```python
setup(
    name='your_package',
    ...
    tests_require=[
        'httptest>=0.1.0'
    ]
)
```

## OIDC Server

Start server `httptest-oidc`

```console
$ python -m httptest.oidc \
    --issuer https://this-service.example.com \
    --audience https://relying-party.example.com \
    --addr 0.0.0.0 \
    --port 8000 \
    --subject test-subject \
    --private-key-pem-path private-key.pem \
    --token-path token.jwt
```

Make requests

```console
$ curl -H "Authorization: Bearer $(curl https://this-service.example.com/token | jq -r token)" -v https://relying-party.example.com
$ curl -H "Authorization: Bearer $(cat token.jwt)" -v https://relying-party.example.com
```

## Cache Server

Run the caceh server and use it's URL in place of the upstream URL whatever you want to intercept on

```console
$ httptest-cache --state-dir .cache/httptest --addr 0.0.0.0 --port 7000 http://localhost:8000
Serving on http://localhost:7000
```

Inspect cached objects in the cache dir

```console
$ python -c 'import sys, pathlib, pickle, pprint; pprint.pprint(pickle.loads(pathlib.Path(sys.argv[-1]).read_bytes()).headers)' .cache/httptest/f31bc77712e808fffdab85a33631e414f25715588b1a026d6b8a4e0171b67e99859ab71b1933c93b0078d1e47da9a929.request.pickle
{'Accept': '*/*',
 'Accept-encoding': 'gzip',
 'Connection': 'close',
 'Content-length': '8159',
 'Content-type': 'application/json',
 'Host': 'localhost:45709',
 'Request-hmac': '1700084205.d96d4f546acedddc142b1168642a74c738685d1ac4aa07984e9a1850bb73ddee',
 'User-agent': 'GitHub-Hookshot/dc69923',
 'X-as': '',
 'X-country': '',
 'X-forwarded-for': '10.56.101.48',
 'X-forwarded-proto': 'https',
 'X-github-delivery': '12dac8d6-83ff-11ee-97c9-119c09045ae0',
 'X-github-event': 'push',
 'X-github-hook-id': '443288828',
 'X-github-hook-installation-target-id': '621131680',
 'X-github-hook-installation-target-type': 'repository',
 'X-github-request-id': '1271:336E:974AC:15C2D2:655539ED',
 'X-glb-edge-region': 'iad',
 'X-glb-edge-site': 'ash1-iad',
 'X-glb-via': 'hostname=glb-proxy-1c66317.ash1-iad.github.net site=ash1-iad '
              'region=iad service=kube-public t=1700084205.902',
 'X-haproxy-ssl-fc-cipher': 'TLS_AES_128_GCM_SHA256',
 'X-haproxy-ssl-fc-protocol': 'TLSv1.3',
 'X-haproxy-ssl-fc-use-keysize': '128',
 'X-real-ip': '10.56.101.48',
 'X-region': '',
 'X-request-start': 't=1700084205915930',
 'X-ssl-ja3-hash': '7a15285d4efc355608b304698cd7f9ab'}
```

## Examples

See the [examples/](https://github.com/pdxjohnny/httptest/tree/master/examples/)
directory for more examples.

- [github_webhook_event_logger.py](https://github.com/pdxjohnny/httptest/blob/master/examples/github_webhook_event_logger.py)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/pdxjohnny/httptest",
    "name": "httptest",
    "maintainer": "John Andersen",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "johnandersenpdx@gmail.com",
    "keywords": "unittesting,testing,http,api,test",
    "author": "John Andersen",
    "author_email": "johnandersenpdx@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/cf/90/4c5c980f576ceceac43264f2f10a505124cfa8dfc0a16b3d4abcfbe54dbf/httptest-2.1.1.tar.gz",
    "platform": null,
    "description": "# httptest\n\n[![Tests](https://github.com/pdxjohnny/httptest/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/pdxjohnny/httptest/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/pdxjohnny/httptest/branch/master/graph/badge.svg)](https://codecov.io/gh/pdxjohnny/httptest)\n\nHTTP testing inspired by golang's httptest package. Supports wrapping asyncio\ncoroutine functions (`async def`).\n\n## Usage\n\n### Context Manager\n\n```python\nimport unittest\nimport urllib.request\n\nimport httptest\n\nclass TestHTTPServer(httptest.Handler):\n\n    def do_GET(self):\n        contents = \"what up\".encode()\n        self.send_response(200)\n        self.send_header(\"Content-type\", \"text/plain\")\n        self.send_header(\"Content-length\", len(contents))\n        self.end_headers()\n        self.wfile.write(contents)\n\ndef main():\n    with httptest.Server(TestHTTPServer) as ts:\n        with urllib.request.urlopen(ts.url()) as f:\n            assert f.read().decode('utf-8') == \"what up\"\n\nif __name__ == '__main__':\n    main()\n```\n\n### Simple HTTP Server Handler\n\n```python\nimport unittest\nimport urllib.request\n\nimport httptest\n\nclass TestHTTPServer(httptest.Handler):\n\n    def do_GET(self):\n        contents = \"what up\".encode()\n        self.send_response(200)\n        self.send_header(\"Content-type\", \"text/plain\")\n        self.send_header(\"Content-length\", len(contents))\n        self.end_headers()\n        self.wfile.write(contents)\n\nclass TestHTTPTestMethods(unittest.TestCase):\n\n    @httptest.Server(TestHTTPServer)\n    def test_call_response(self, ts=httptest.NoServer()):\n        with urllib.request.urlopen(ts.url()) as f:\n            self.assertEqual(f.read().decode('utf-8'), \"what up\")\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n### Serve Files\n\n```python\nimport pathlib\nimport unittest\nimport http.server\nimport urllib.request\n\nimport httptest\n\nFILE_PATH = pathlib.Path(__file__)\n\nclass TestHTTPTestMethods(unittest.TestCase):\n\n    @httptest.Server(\n        lambda *args: http.server.SimpleHTTPRequestHandler(\n            *args, directory=FILE_PATH.parent\n        )\n    )\n    def test_call_response(self, ts=httptest.NoServer()):\n        with urllib.request.urlopen(ts.url() + FILE_PATH.name) as f:\n            self.assertEqual(f.read().decode('utf-8'), FILE_PATH.read_text())\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n### Asyncio Support\n\nAsyncio support for the unittest package hasn't yet landed in Python.\n[python/issue32972](https://bugs.python.org/issue32972).\nIt should land in 3.8, check it out\n[here](https://github.com/python/cpython/pull/13386).\n\nIf you want a quick way to add `asyncio` test cases you can import the helper\nfrom [intel/dffml](https://github.com/intel/dffml).\n\n```python\nimport sys\nimport unittest\nimport urllib.request\nif sys.version_info.minor == 3 \\\n        and sys.version_info.minor <= 7:\n    from dffml.util.asynctestcase import AsyncTestCase\nelse:\n    # In Python 3.8\n    from unittest import IsolatedAsyncioTestCase as AsyncTestCase\n\nimport httptest\n\nclass TestHTTPServer(httptest.Handler):\n\n    def do_GET(self):\n        contents = \"what up\".encode()\n        self.send_response(200)\n        self.send_header(\"Content-type\", \"text/plain\")\n        self.send_header(\"Content-length\", len(contents))\n        self.end_headers()\n        self.wfile.write(contents)\n\nclass TestHTTPTestMethods(AsyncTestCase):\n\n    @httptest.Server(TestHTTPServer)\n    async def test_call_response(self, ts=httptest.NoServer()):\n        with urllib.request.urlopen(ts.url()) as f:\n            self.assertEqual(f.read().decode('utf-8'), \"what up\")\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\nIn your project's `setup.py`, add `dffml` in `tests_require`.\n\n```python\nsetup(\n    name='your_package',\n    ...\n    tests_require=[\n        'httptest>=0.1.0',\n        'dffml>=0.4.0.post0'\n    ]\n)\n```\n\n## Auto Install\n\nIf you're making a python package, you'll want to add `httptest` to your\n`setup.py` file's `tests_require` section.\n\nThis way, when your run `python setup.py test` setuptools will install\n`httptest` for you in a package local directory, if it's not already installed.\n\n```python\nsetup(\n    name='your_package',\n    ...\n    tests_require=[\n        'httptest>=0.1.0'\n    ]\n)\n```\n\n## OIDC Server\n\nStart server `httptest-oidc`\n\n```console\n$ python -m httptest.oidc \\\n    --issuer https://this-service.example.com \\\n    --audience https://relying-party.example.com \\\n    --addr 0.0.0.0 \\\n    --port 8000 \\\n    --subject test-subject \\\n    --private-key-pem-path private-key.pem \\\n    --token-path token.jwt\n```\n\nMake requests\n\n```console\n$ curl -H \"Authorization: Bearer $(curl https://this-service.example.com/token | jq -r token)\" -v https://relying-party.example.com\n$ curl -H \"Authorization: Bearer $(cat token.jwt)\" -v https://relying-party.example.com\n```\n\n## Cache Server\n\nRun the caceh server and use it's URL in place of the upstream URL whatever you want to intercept on\n\n```console\n$ httptest-cache --state-dir .cache/httptest --addr 0.0.0.0 --port 7000 http://localhost:8000\nServing on http://localhost:7000\n```\n\nInspect cached objects in the cache dir\n\n```console\n$ python -c 'import sys, pathlib, pickle, pprint; pprint.pprint(pickle.loads(pathlib.Path(sys.argv[-1]).read_bytes()).headers)' .cache/httptest/f31bc77712e808fffdab85a33631e414f25715588b1a026d6b8a4e0171b67e99859ab71b1933c93b0078d1e47da9a929.request.pickle\n{'Accept': '*/*',\n 'Accept-encoding': 'gzip',\n 'Connection': 'close',\n 'Content-length': '8159',\n 'Content-type': 'application/json',\n 'Host': 'localhost:45709',\n 'Request-hmac': '1700084205.d96d4f546acedddc142b1168642a74c738685d1ac4aa07984e9a1850bb73ddee',\n 'User-agent': 'GitHub-Hookshot/dc69923',\n 'X-as': '',\n 'X-country': '',\n 'X-forwarded-for': '10.56.101.48',\n 'X-forwarded-proto': 'https',\n 'X-github-delivery': '12dac8d6-83ff-11ee-97c9-119c09045ae0',\n 'X-github-event': 'push',\n 'X-github-hook-id': '443288828',\n 'X-github-hook-installation-target-id': '621131680',\n 'X-github-hook-installation-target-type': 'repository',\n 'X-github-request-id': '1271:336E:974AC:15C2D2:655539ED',\n 'X-glb-edge-region': 'iad',\n 'X-glb-edge-site': 'ash1-iad',\n 'X-glb-via': 'hostname=glb-proxy-1c66317.ash1-iad.github.net site=ash1-iad '\n              'region=iad service=kube-public t=1700084205.902',\n 'X-haproxy-ssl-fc-cipher': 'TLS_AES_128_GCM_SHA256',\n 'X-haproxy-ssl-fc-protocol': 'TLSv1.3',\n 'X-haproxy-ssl-fc-use-keysize': '128',\n 'X-real-ip': '10.56.101.48',\n 'X-region': '',\n 'X-request-start': 't=1700084205915930',\n 'X-ssl-ja3-hash': '7a15285d4efc355608b304698cd7f9ab'}\n```\n\n## Examples\n\nSee the [examples/](https://github.com/pdxjohnny/httptest/tree/master/examples/)\ndirectory for more examples.\n\n- [github_webhook_event_logger.py](https://github.com/pdxjohnny/httptest/blob/master/examples/github_webhook_event_logger.py)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Add unit tests to your http client",
    "version": "2.1.1",
    "project_urls": {
        "Homepage": "https://github.com/pdxjohnny/httptest"
    },
    "split_keywords": [
        "unittesting",
        "testing",
        "http",
        "api",
        "test"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "39840ecd7e95b7485a7b1b378aad8ac50e389934bdac734100ca519a28fa20a3",
                "md5": "198ee93d0d0e1f080342e95dc62d2044",
                "sha256": "74e5fed152c6982a1bbb79549ceb142251d467825e20d4677096c1412e94aa81"
            },
            "downloads": -1,
            "filename": "httptest-2.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "198ee93d0d0e1f080342e95dc62d2044",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 12191,
            "upload_time": "2024-01-03T19:26:18",
            "upload_time_iso_8601": "2024-01-03T19:26:18.474936Z",
            "url": "https://files.pythonhosted.org/packages/39/84/0ecd7e95b7485a7b1b378aad8ac50e389934bdac734100ca519a28fa20a3/httptest-2.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cf904c5c980f576ceceac43264f2f10a505124cfa8dfc0a16b3d4abcfbe54dbf",
                "md5": "f91c8c14997f394c2cb60df1933dd6fc",
                "sha256": "a473681c2c280422a3fb61e6d631835ba1bc53ca1d90b256838d348b17ab73e7"
            },
            "downloads": -1,
            "filename": "httptest-2.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "f91c8c14997f394c2cb60df1933dd6fc",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 19052,
            "upload_time": "2024-01-03T19:26:19",
            "upload_time_iso_8601": "2024-01-03T19:26:19.605355Z",
            "url": "https://files.pythonhosted.org/packages/cf/90/4c5c980f576ceceac43264f2f10a505124cfa8dfc0a16b3d4abcfbe54dbf/httptest-2.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-03 19:26:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "pdxjohnny",
    "github_project": "httptest",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "httptest"
}
        
Elapsed time: 0.16225s