Skip to main content

a pytest plugin for Sanic

Project description

Build

Travis-CI Build Status

Docs

Documentation Status

Package

PyPI Package latest release PyPI Wheel Supported versions Supported implementations

A pytest plugin for Sanic. It helps you to test your code asynchronously.

This plugin provides:

  • very easy testing with async coroutines

  • common and useful fixtures

  • asynchronous fixture support

  • test_client/sanic_client for Sanic application

  • test_server for Sanic application

You can find out more here:

http://pytest-sanic.readthedocs.io/en/latest/

Releases and change logs can be found here:

https://github.com/x-amer-ngmx/rt-pytest-sanic/releases

Install

pip install rt-pytest-sanic

Quick Start

You don’t have to load rt-pytest-sanic explicitly. pytest will do it for you.

You can set up a fixture for your app like this:

import pytest
from .app import create_app

@pytest.yield_fixture
def app():
    app = create_app(test_config, **params)
    yield app

This app fixture can then be used from tests:

async def test_sanic_db_find_by_id(app):
    """
    Let's assume that, in db we have,
        {
            "id": "123",
            "name": "Kobe Bryant",
            "team": "Lakers",
        }
    """
    doc = await app.db["players"].find_by_id("123")
    assert doc.name == "Kobe Bryant"
    assert doc.team == "Lakers"

To send requests to your app, you set up a client fixture using the loop and sanic_client fixtures:

@pytest.fixture
def test_cli(loop, app, sanic_client):
    return loop.run_until_complete(sanic_client(app))

This test_cli fixture can then be used to send requests to your app:

async def test_index(test_cli):
    resp = await test_cli.get('/')
    assert resp.status_code == 200

async def test_player(test_cli):
    resp = await test_cli.get('/player')
    assert resp.status_code == 200

asynchronous fixture

rt-pytest-sanic also supports asynchronous fixtures, just writes them like common pytest fixtures.

@pytest.fixture
async def async_fixture_sleep():
    await asyncio.sleep(0.1)
    return "sleep..."

Fixtures

Some fixtures for easy testing.

loop

rt-pytest-sanic creates an event loop and injects it as a fixture. pytest will use this event loop to run your async tests. By default, fixture loop is an instance of asyncio.new_event_loop. But uvloop is also an option for you, by simpy passing --loop uvloop. Keep mind to just use one single event loop.

unused_port

an unused TCP port on the localhost.

test_server

Creates a TestServer instance by giving a Sanic application. It’s very easy to utilize test_server to create your Sanic application server for testing.

@pytest.yield_fixture
def app():
    app = Sanic("test_sanic_app")

    @app.route("/test_get", methods=['GET'])
    async def test_get(request):
        return response.json({"GET": True})

    yield app

@pytest.fixture
def sanic_server(loop, app, test_server):
    return loop.run_until_complete(test_server(app))

You can also very easily override this loop fixture by creating your own, simply like,

@pytest.yield_fixture
def loop():
    loop = MyEventLoop()
    yield loop
    loop.close()

test_client

test_client has been deprecated, please use sanic_client instead, check out issue for more context.

sanic_client

Creates a TestClient instance by giving a Sanic application. You can simply have a client by using sanic_client, like

@pytest.yield_fixture
def app():
    app = Sanic("test_sanic_app")

    @app.route("/test_get", methods=['GET'])
    async def test_get(request):
        return response.json({"GET": True})

    @app.route("/test_post", methods=['POST'])
    async def test_post(request):
        return response.json({"POST": True})

    @app.route("/test_put", methods=['PUT'])
    async def test_put(request):
        return response.json({"PUT": True})

    @app.route("/test_delete", methods=['DELETE'])
    async def test_delete(request):
        return response.json({"DELETE": True})

    @app.route("/test_patch", methods=['PATCH'])
    async def test_patch(request):
        return response.json({"PATCH": True})

    @app.route("/test_options", methods=['OPTIONS'])
    async def test_options(request):
        return response.json({"OPTIONS": True})

    @app.route("/test_head", methods=['HEAD'])
    async def test_head(request):
        return response.json({"HEAD": True})

    @app.websocket("/test_ws")
    async def test_ws(request, ws):
        data = await ws.recv()
        await ws.send(data)

    yield app

@pytest.fixture
def test_cli(loop, app, sanic_client):
    return loop.run_until_complete(sanic_client(app, protocol=WebSocketProtocol))

#########
# Tests #
#########

async def test_fixture_test_client_get(test_cli):
    """
    GET request
    """
    resp = await test_cli.get('/test_get')
    assert resp.status_code == 200
    resp_json = resp.json()
    assert resp_json == {"GET": True}

async def test_fixture_test_client_post(test_cli):
    """
    POST request
    """
    resp = await test_cli.post('/test_post')
    assert resp.status_code == 200
    resp_json = resp.json()
    assert resp_json == {"POST": True}

async def test_fixture_test_client_put(test_cli):
    """
    PUT request
    """
    resp = await test_cli.put('/test_put')
    assert resp.status_code == 200
    resp_json = resp.json()
    assert resp_json == {"PUT": True}

async def test_fixture_test_client_delete(test_cli):
    """
    DELETE request
    """
    resp = await test_cli.delete('/test_delete')
    assert resp.status_code == 200
    resp_json = resp.json()
    assert resp_json == {"DELETE": True}

async def test_fixture_test_client_patch(test_cli):
    """
    PATCH request
    """
    resp = await test_cli.patch('/test_patch')
    assert resp.status_code == 200
    resp_json = resp.json()
    assert resp_json == {"PATCH": True}

async def test_fixture_test_client_options(test_cli):
    """
    OPTIONS request
    """
    resp = await test_cli.options('/test_options')
    assert resp.status_code == 200
    resp_json = resp.json()
    assert resp_json == {"OPTIONS": True}

async def test_fixture_test_client_head(test_cli):
    """
    HEAD request
    """
    resp = await test_cli.head('/test_head')
    assert resp.status_code == 200
    resp_json = resp.json()
    # HEAD should not have body
    assert resp_json is None

async def test_fixture_test_client_ws(test_cli):
    """
    Websockets
    """
    ws_conn = await test_cli.ws_connect('/test_ws')
    data = 'hello world!'
    await ws_conn.send(data)
    msg = await ws_conn.recv()
    assert msg == data
    await ws_conn.close()

small notes:

test_cli.ws_connect does not work in sanic.__version__ <= '0.5.4', because of a Sanic bug, but it has been fixed in master branch. And websockets.__version__ >= '4.0' has broken websockets in sanic.__version__ <= '0.6.0', but it has been fixed in master.

Tips

  • Blueprints Testing

  • test_cli.ws_connect does not work in sanic.__version__ <= '0.5.4', because of a Sanic bug, but it has been fixed in master branch.

  • Importing app has loop already running when you have db_init listeners.

  • Incorrect coverage report with pytest-cov, but we can have workarounds for this issue, it’s a pytest loading plugin problem essentially.

  • Websockets > 4.0 has broken websockets in sanic.__version__ <= '0.6.0', but it has been fixed in this commit

Feel free to create issue if you have any question. You can also check out closed issues

Development

rt-pytest-sanic accepts contributions on GitHub, in the form of issues or pull requests.

Build.

poetry install

Run unit tests.

poetry run pytest ./tests --cov rt_pytest_sanic

Reference

Some useful pytest plugins:

Project details


Download files

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

Source Distribution

rt_pytest_sanic-2.0.0.tar.gz (13.6 kB view details)

Uploaded Source

Built Distribution

rt_pytest_sanic-2.0.0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file rt_pytest_sanic-2.0.0.tar.gz.

File metadata

  • Download URL: rt_pytest_sanic-2.0.0.tar.gz
  • Upload date:
  • Size: 13.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.8.18

File hashes

Hashes for rt_pytest_sanic-2.0.0.tar.gz
Algorithm Hash digest
SHA256 c453e9fee90eb24719d8d2e7cf043cda43349880abc485bfd4bf0bbf0602c3bf
MD5 a3181c1d4dc353e870adf12380892f9f
BLAKE2b-256 ed4df9a81142fc93cc19067d55995e172da54644a0e855c34668b580e750fb57

See more details on using hashes here.

File details

Details for the file rt_pytest_sanic-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for rt_pytest_sanic-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bdac72bd1a1709a5e5786367b1fa54a26e9b4c1554c43d54d2a1f7a096078f59
MD5 fa87d8fca77090a5e4d78008113dac48
BLAKE2b-256 f83670935ecc739d016cd508a346e694f9c2ceb6f3844737fb1c4de3924b0bed

See more details on using hashes here.

Supported by

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