Skip to content

Testing#

TestClient and AsyncTestClient drive your real application in-process — no server, no sockets, no network flakiness. This is the exact tooling Hayavega's own 415+ tests run on.

WSGI: TestClient#

import unittest
from hayavega.runtime import create_application
from hayavega.testing import TestClient
from .routes import urlpatterns


class ViewTests(unittest.TestCase):
    def setUp(self):
        self.client = TestClient(create_application(urlpatterns))

    def test_list_posts(self):
        response = self.client.get("posts")
        self.assertEqual(response.status_code, 200)
        self.assertIn("title", response.json()[0])

    def test_create_post_requires_csrf(self):
        response = self.client.post("posts", form_data={"title": "Hello"})
        self.assertEqual(response.status_code, 403)

TestResponse gives you .status_code, .headers, .content (bytes), .text, and .json(). .get(path, **kwargs)/.post(path, **kwargs) accept json_data=, form_data=, and headers=; cookies (session tokens, CSRF) are tracked automatically across requests on the same client instance.

ASGI: AsyncTestClient#

from hayavega.asgi import create_asgi_application
from hayavega.testing import AsyncTestClient

client = AsyncTestClient(create_asgi_application(urlpatterns))
response = client.get("/api/items")  # synchronous from the caller's perspective

Drives a real ASGI 3.0 scope/receive/send cycle, one asyncio.run() per call — no pytest-async plugin required.

Testing the Rust runtime#

Requires hayavega_native to be built. Since it's a real server (not a WSGI-callable-in-process client), tests hit it over a real socket:

import socket, threading, time
from hayavega.rust_runtime import serve

thread = threading.Thread(target=lambda: serve(urlpatterns, "127.0.0.1", port, workers=4), daemon=True)
thread.start()
# ... poll until the port accepts connections, then use urllib/socket directly

See tests/test_rust_runtime.py (functional parity against TestClient) and tests/test_rust_runtime_hardening.py (adversarial requests over raw sockets — malformed Content-Length, chunked encoding, CRLF injection) for the real pattern this project uses.

Database fixtures#

Most model tests use a real, temporary SQLite file per test case rather than mocking the database layer:

import tempfile
from pathlib import Path


class ModelTests(unittest.TestCase):
    def setUp(self):
        self.directory = tempfile.TemporaryDirectory()
        self.database = Path(self.directory.name) / "test.sqlite3"

    def tearDown(self):
        self.directory.cleanup()

PostgreSQL and MySQL/MariaDB integration tests are opt-in and run against real service containers in CI, not mocks — see tests/test_postgresql_integration.py and tests/test_mysql_integration.py.

Running the suite#

pytest                    # full suite, coverage-gated
ruff check . && ruff format --check .
mypy