7. Testing

rational.testing drives your pages through the real HTTP endpoints (sessions, CSRF, validation, patches) without a browser. Install the testing extra and pytest.

tests/conftest.py
"""Test setup: import the project like ``python app.py`` does, with a throwaway database."""

import os
import sys
import tempfile
from pathlib import Path

PROJECT_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_DIR))

# Settings are read when the app is imported, so point them at a temporary database first.
os.environ["DATABASE_URL"] = f"sqlite+aiosqlite:///{(Path(tempfile.mkdtemp()) / 'test.db').as_posix()}"
tests/test_guestbook.py
from app import app

from rational.testing import TestClient


def test_sign_the_guestbook():
    with TestClient(app) as client:  # "with" runs the startup hooks: the tables get created
        page = client.open("/")
        page.submit({"name": "Ada", "message": "Hello from the tests!"})
        assert "Hello from the tests!" in page.text


def test_validation_messages():
    with TestClient(app) as client:
        page = client.open("/")
        data = page.submit({"name": "", "message": "Visit https://spam.example"})
        fields = {error["field"] for error in data["error"]["details"]}
        assert fields == {"name"}  # the form model rejects the empty name first
        data = page.submit({"name": "Bot", "message": "Visit https://spam.example"})
        assert data["error"]["details"] == [{"field": "message", "message": "Links are not allowed"}]


def test_likes_are_per_card():
    with TestClient(app) as client:
        page = client.open("/")
        page.submit({"name": "Grace", "message": "First"})
        page.submit({"name": "Alan", "message": "Second"})
        page.click("♥ 0")
        assert page.text.count("♥ 1") == 1 and page.text.count("♥ 0") >= 1


def test_the_json_api():
    with TestClient(app) as client:
        client.http.post("/api/guestbook/sign", json={"name": "API", "message": "Signed over HTTP"})
        entries = client.http.get("/api/guestbook/entries").json()
        assert entries[0]["message"] == "Signed over HTTP"
$ pytest
....                                                    [100%]

What you can do with a page

page.click("♥ 0")

Finds the element handling clicks whose accessible name (aria-label, or its text) contains the text, and fires the event.

page.input("Your name", "Ada")

Types into the control labelled “Your name”.

page.submit({...})

Submits a form with the given fields.

page.text / page.html / page.dom

The page after every patch so far.

Each action returns the server’s JSON response, so you can also assert on validation errors or navigation. Use with TestClient(app) when the app has startup hooks, such as creating tables.

Next: 8. Deploying.