def my_add(x, y):
return x + yTesting
Slide Contents
title: “Testing Shiny Apps”
Why test?
- Manual “click around and check” doesn’t scale
- Catch regressions before users do
- Document expected behavior as executable code
Visual check + assert statements
my_add(3, 10)13
assert my_add(3, 10) == 13assert my_add(3, 10) == 100--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) Cell In[4], line 1 ----> 1 assert my_add(3, 10) == 100 AssertionError:
Unit testing - pytest
- Need to know a bit of how packages are set up
Python functions + Shiny
- If you have a normal python function, you can still pass in reactive values as the arguments!
- Import your existing functions / modules
- Use other packages
- As long as you don’t plan on having reactive calls inside the function body
- Remember: reactive things must happen inside a reactive context
pytestis the package / tool to do unit testing
Playwright - end-to-end testing
Playwright is a modern browser automation library.
Shiny provides a Playwright controller that understands Shiny-specific widgets:
from playwright.sync_api import Page
from shiny.playwright import controller
from shiny.run import ShinyAppProcTest structure
from pathlib import Path
from playwright.sync_api import Page
from shiny.playwright import controller
from shiny.run import run_shiny_app
APP = Path(__file__).parent / "app.py"
def test_species_filter(page: Page):
with run_shiny_app(str(APP)) as app:
page.goto(app.url)
radio = controller.InputRadioButtons(page, "species")
text = controller.OutputText(page, "n_rows")
radio.expect_choices(["Adelie", "Chinstrap", "Gentoo"])
radio.set("Chinstrap")
text.expect_value("68 penguins")Running tests
code/demos/testing/
├── app.py ← the Shiny app
└── test_app.py ← tests
cd code/demos/testing
pytestOr from the repo root:
pytest code/demos/testing/Controllers
Shiny provides typed controllers for every input and output type:
# Inputs
controller.InputRadioButtons(page, "id")
controller.InputSlider(page, "id")
controller.InputSelect(page, "id")
controller.InputCheckboxGroup(page, "id")
# Outputs
controller.OutputPlot(page, "id")
controller.OutputText(page, "id")
controller.OutputDataFrame(page, "id")What to test
- Default state renders without errors
- Each input change produces the expected output change
- Edge cases: empty selections, out-of-range values
- Error messages appear when they should