Testing components
Test each behavior at the smallest useful layer. Python tests are quick and good at checking component input and rendered HTML. A host framework's test client checks HTTP integration. A browser test proves that Alpine, Citry's client runtime, and real DOM events work together.
Give each test its own Citry instance
A component registers when Python defines its class. If tests define temporary components on the shared default engine, their names remain registered for later tests and may collide.
Create a fresh Citry instance instead:
from citry import Citry, Component
def test_greeting():
app = Citry(autodiscover=False)
class Greeting(Component):
class Kwargs:
name: str
citry = app
template = """
<p>Hello {{ name }}!</p>
"""
html = str(Greeting(name="World"))
assert "Hello World!" in html
Set autodiscover=False when the test defines every component it needs. This keeps the test independent of project directories and imports.
For several tests, put the engine in a fixture and let each test define the components it needs on that engine:
import pytest
from citry import Citry
@pytest.fixture
def app():
return Citry(autodiscover=False)
Assert the result the reader can observe
Prefer focused checks for text, attributes, and ordering:
html = str(Badge(label="Ready", tone="success"))
assert ">Ready<" in html
assert 'class="badge badge--success"' in html
Citry may add attributes needed by its browser runtime. Those attributes are implementation details, so avoid exact comparisons against the entire HTML string unless the exact serialization is the behavior under test.
An HTML parser can make structural assertions easier when whitespace and attribute order do not matter:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
badge = soup.select_one(".badge")
assert badge is not None
assert badge.get_text(strip=True) == "Ready"
Use whichever parser your application already depends on. Citry does not require Beautiful Soup for tests.
Test inputs and slots
Render representative values, defaults, and boundary cases. Also check that invalid calls fail in the way your public component contract promises:
import pytest
from citry import Citry, Component, SlotInput
def test_notice_requires_a_message():
app = Citry(autodiscover=False)
class Notice(Component):
class Kwargs:
message: str
class Slots:
actions: SlotInput | None = None
citry = app
template = """
<aside>
<p>{{ message }}</p>
<c-slot name="actions" />
</aside>
"""
with pytest.raises(TypeError):
str(Notice())
Test slot content through the public slots mapping:
html = str(
Notice(
message="Saved",
slots={"actions": "Undo"},
)
)
assert "Saved" in html
assert "Undo" in html
Test several components together
A component can render registered children only when they belong to the same engine. Define the small component family on one test engine and render the outer component:
from citry import Citry, Component
def test_profile_card_contains_the_avatar():
app = Citry(autodiscover=False)
class Avatar(Component):
class Kwargs:
name: str
citry = app
template = """
<span class="avatar">{{ name[:1] }}</span>
"""
class ProfileCard(Component):
class Kwargs:
name: str
citry = app
template = """
<article>
<c-avatar c-name="name" />
<h2>{{ name }}</h2>
</article>
"""
html = str(ProfileCard(name="Ada"))
assert 'class="avatar"' in html
assert ">Ada</h2>" in html
This checks composition, input forwarding, template lookup, and final output without depending on an HTTP server.
Choose the right test for interactive behavior
Rendering in Python proves which HTML, bindings, and assets Citry produces. It does not execute Alpine or Citry's browser runtime.
- Use a Python render test for component inputs and initial HTML.
- Use your framework's test client for mounted Citry routes, event requests, response status, and returned actions.
- Use a browser test for clicks, reactive state, focus, DOM updates, and event bubbling.
For server events, keep the handler's business logic in ordinary Python functions when practical. Test those functions directly, then add a smaller integration test for the Citry event boundary. See Events and Web frameworks.
For browser behavior, exercise the page as a person would: click the visible control and assert the visible result. Avoid reaching into Citry's internal DOM attributes or JavaScript registries.