Skip to content

Testing

FluxUI ships a FluxTestClient that renders pages and dispatches events without a real browser or network connection. Tests are fully synchronous — no asyncio.run or pytest-asyncio setup required.


Installation

The test client is included in the base fluxui package. No extra install needed. For the test runner, install pytest:

pip install pytest

FluxTestClient

from fluxui.testing import FluxTestClient

Constructor

FluxTestClient(app: FluxApp)

Use as a context manager to ensure the internal event loop is cleaned up:

with FluxTestClient(app) as client:
    ...

Methods

connect(route="/")

Renders the page at route and establishes a fake session. Must be called before any other method.

client.connect("/")
client.connect("/user/42")

html(component_id=None)

Returns the current rendered HTML. If component_id is given, returns only that component's HTML.

full_html   = client.html()
metric_html = client.html("my-metric")

text(component_id)

Returns the visible text content of a component with all HTML tags stripped.

assert client.text("counter") == "0"

click(label_or_id)

Finds a Button by its label text or component ID and fires a click event.

client.click("Increment")
client.click("my-button-id")

input(label_or_id, value)

Types a value into a TextInput, NumberInput, or Textarea. Finds the component by its label= text or component ID.

client.input("Username", "alice")
client.input("Amount", "42")

select(label_or_id, value)

Selects an option in a Select component.

client.select("Status", "active")

check(label_or_id, checked=True)

Toggles a Checkbox or Switch.

client.check("Enable notifications", True)
client.check("Dark mode", False)

Examples

Counter test

import fluxui as ui
from fluxui.testing import FluxTestClient


def make_counter_app():
    app = ui.App("test")

    @app.page("/")
    def home(session):
        count = ui.state(0)
        ui.Button("Increment", on_click=lambda: count.set(count.value + 1))
        ui.Button("Reset",     on_click=lambda: count.set(0))
        ui.Text(lambda: str(count.value), id="counter")

    return app


def test_counter_increments():
    with FluxTestClient(make_counter_app()) as client:
        client.connect("/")
        assert client.text("counter") == "0"

        client.click("Increment")
        assert client.text("counter") == "1"

        client.click("Increment")
        client.click("Increment")
        assert client.text("counter") == "3"


def test_counter_resets():
    with FluxTestClient(make_counter_app()) as client:
        client.connect("/")
        client.click("Increment")
        client.click("Increment")
        assert client.text("counter") == "2"

        client.click("Reset")
        assert client.text("counter") == "0"

Form validation test

def test_form_shows_error_on_empty_submit():
    app = ui.App("test")

    @app.page("/")
    def home(session):
        name  = ui.state("")
        error = ui.state("")

        def submit():
            if not name.peek().strip():
                error.set("Name is required")

        ui.TextInput(label="Name", value=name, on_change=name.set)
        ui.Text(error, id="error-msg")
        ui.Button("Submit", on_click=submit)

    with FluxTestClient(app) as client:
        client.connect("/")
        assert client.text("error-msg") == ""

        client.click("Submit")
        assert client.text("error-msg") == "Name is required"

        client.input("Name", "Alice")
        client.click("Submit")
        assert client.text("error-msg") == ""

Routing test

def test_navigation():
    app = ui.App("test")

    @app.page("/")
    def home(session):
        ui.Text("Home page", id="page-title")
        ui.Button("Go to about", on_click=lambda: session.navigate("/about"))

    @app.page("/about")
    def about(session):
        ui.Text("About page", id="page-title")

    with FluxTestClient(app) as client:
        client.connect("/")
        assert "Home page" in client.html()

        # Directly connect to the about page
        client.connect("/about")
        assert "About page" in client.html()

DataGrid and Select test

def test_filtered_grid():
    USERS = [
        {"name": "Alice", "role": "admin"},
        {"name": "Bob",   "role": "user"},
        {"name": "Carol", "role": "admin"},
    ]

    app = ui.App("test")

    @app.page("/")
    def home(session):
        role_filter = ui.state("all")

        def rows():
            if role_filter.value == "all":
                return USERS
            return [u for u in USERS if u["role"] == role_filter.value]

        ui.Select(
            label="Role",
            options=["all", "admin", "user"],
            value=role_filter,
            on_change=role_filter.set,
        )
        ui.DataGrid(
            data=rows,
            columns=[{"key": "name"}, {"key": "role"}],
        )

    with FluxTestClient(app) as client:
        client.connect("/")
        full = client.html()
        assert "Alice" in full and "Bob" in full and "Carol" in full

        client.select("Role", "admin")
        filtered = client.html()
        assert "Alice" in filtered
        assert "Carol" in filtered
        assert "Bob" not in filtered

Tips

  • Use explicit id= on components you want to assert on with client.text().
  • client.html() returns the merged HTML after all reactive updates have flushed.
  • Each FluxTestClient instance creates an isolated event loop — tests are fully independent.
  • You can reuse an app instance across tests but create a fresh FluxTestClient for each test.