DocsGuides
Testing
nx test runs the server tests with pytest, and the client tests with vitest when you ask for them.
nx test # pytest
nx test -k create_user # extra arguments go to pytest
nx test --client # pytest, then vitest
nx test --no-server --client # vitest only
What runs
| Part | Command | Condition |
|---|---|---|
| Server | pytest |
runs on tests/ when it exists, otherwise on the project root; skipped with a warning when pytest is not installed |
| Client | npx vitest run |
only with --client, and only when vitest.config.ts exists or vitest appears in package.json |
There is no built-in end-to-end runner. Drive the server with TestClient and add Playwright or
Cypress yourself for browser behaviour.
Test the server
The test client talks to the ASGI app in-process: no server to start, no port to pick.
from fastapi.testclient import TestClient
from nexy.app import app
def test_home_renders():
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200nexy.app.app is the assembled application, built the first time it is read. It reads
nexyconfig.py from the working directory, so run pytest from the project root.
When a test changes configuration, routes or configuration, build a fresh application instead of reusing the module-level one:
from fastapi.testclient import TestClient
from nexy.modules.routers.app import AppServer
def test_api_hello():
with TestClient(AppServer().run()) as client:
response = client.get("/api/hello")
assert response.json() == {"message": "Hello"}
AppServer().run() reassembles routes, middleware, sessions and translations, so each call starts
from the current state of the project directory.
What to assert
A page returns HTML. Assert the status, then the content you care about:
def test_blog_page_renders_slug(client):
response = client.get("/blog/post-1")
assert "post-1" in response.text
An API handler returns JSON. A validation failure is a 422, and a missing route is a 404
answered by notfound.nexy when that file exists:
def test_create_item_validates_body(client):
assert client.post("/api/items", json={}).status_code == 422
For an action, test the function directly - it is a normal function - and test the HTTP endpoint by reading its generated path from the page that exposes it:
from src.actions.user import create_user
async def test_create_user_returns_name():
assert await create_user("Ada") == {"name": "Ada"}
def test_form_posts_to_action(client):
html = client.get("/").text
action = html.split('action="')[1].split('"')[0]
response = client.post(action, data={"name": "Ada"}, follow_redirects=False)
assert response.status_code == 303
A call with JSON answers with the result, and 422 with the invalid fields.
Test a .nexy component
nexy.testing.render compiles a component, with the components it imports, and returns its HTML.
Props are keyword arguments and children= fills the slot:
from nexy.testing import render
def test_button_shows_its_label():
assert "Save" in render("src/components/button.nexy", label="Save")Run pytest from the project root: the compiled files go to __nexy__/, as with nx dev. A
component that reads the request (usePathname, useSession) needs a page test with TestClient
instead.
Test the client
nx test --client expects your own vitest setup. A minimal component test:
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import Counter from "./counter";
describe("Counter", () => {
it("starts at zero", () => {
const { getByText } = render(<Counter start={0} />);
expect(getByText("0")).toBeTruthy();
});
});vitest and the testing library are project dependencies: nexy new does not install a test stack.
Add vitest.config.ts with the environment you need (jsdom or happy-dom for components).
Continuous integration
Two commands are enough for a project pipeline:
nx build
nx test
nx build fails on a compilation error; nx test fails on a failing assertion and exits non-zero.
nx check adds mypy and tsc to the run.
Working on Nexy itself uses different commands: see CONTRIBUTING.