Skip to content

Testing

Running Tests Locally

Your app’s specs live under tests/specs/ and run through the framework’s built-in runner. There are two equal ways to drive it: the wheels CLI from your terminal, or the runner’s URL from any browser or HTTP client — the CLI calls that same URL under the hood, so nothing about your tooling choice changes what runs.

You’ll learn:

  • Running the suite with the wheels CLI — and without it
  • Filtering by directory or a single spec, in both paths
  • How the test database behaves
  • The common failure modes and their fixes

wheels test detects your running dev server, reloads the app, hits the test runner, and prints pass/fail counts. A failing, errored, rejected, vacuous, or compile-skipped run throws Wheels.TestsFailed (non-zero exit) — do not treat a quiet 0 passed as success. No ceremony:

your shell — in your app root
# Run the full suite
wheels test
# Filter by directory (positional or --filter)
wheels test models
wheels test --filter=controllers
# Run one spec file, by name or by dotted path
wheels test --filter=UserSpec
wheels test --filter=tests.specs.models.UserSpec
# Pick a reporter — simple (default), json, tap
wheels test --reporter=tap
# CI mode: emits GitHub Actions ::error annotations per failed/errored spec
wheels test --ci

The supported flags are --filter=<dir>, --reporter=<simple|json|tap>, --verbose / -v, --ci, --no-test-db (don’t auto-swap to the <datasource>_test test database), --base-path=<path> (URL prefix for subfolder-mounted apps — auto-derived from WHEELS_SUBPATH or set(subpath=...) when omitted), and --test-timeout=<seconds> (how long to wait for the suite, default 900; also WHEELS_TEST_TIMEOUT). A positional argument acts as the filter. There’s also --core (with --db=) for running the framework’s own suite — see the framework-testing page.

The runner is a URL. With your app running in the development environment — under CommandBox, your own IIS/nginx setup, anything — hit:

browser or curl
# Run everything (browser-friendly HTML report)
http://localhost:8080/wheels/app/tests
# Machine-readable, and exactly what the CLI consumes
curl "http://localhost:8080/wheels/app/tests?format=json"
# Filter by directory (dot-notation, rooted at your app's tests/ mapping)
curl "http://localhost:8080/wheels/app/tests?format=json&directory=tests.specs.models"

Keep the dev loop as: change a spec, refresh the URL. Two things to know:

  • Development only. The /wheels/* surfaces are gated by a development-only allowlist (since 4.0.4, #2903) — in any other environment they return 404. Run tests where the app runs as development (locally, or a CI job configured that way — see CI Integration).
  • The datasource is whatever the app is using. The <datasource>_test auto-swap is a CLI convenience; the raw runner exercises the datasource your app is currently configured with. If you drive tests by URL routinely, point your development datasource at a database you’re happy to have tests/populate.cfm rebuild — or keep populate idempotent (it should be anyway; see Fixtures & Test Data).
  • Isolated application scope. With the stock Application.cfc snippet (include of vendor/wheels/events/testcontext.cfm after config/app.cfm), /wheels/app/tests and /wheels/core/tests bind a separate CFML application name. Concurrent browsing of the same server keeps live config. See Fixtures & Test Data.

An unrecognized directory= value is ignored and the full suite runs instead (#3083) — the JSON flags this with directoryRejected: true. A directory= that names a single spec file runs that file as one bundle (#3759); one that names nothing (or a folder with no *Spec.cfc files) yields bundlesDiscovered: 0. wheels test throws Wheels.TestsFailed in both cases. If you curl the runner yourself, check those fields; if a filtered run looks suspiciously slow or broad, check the value against your tests/specs/ layout.

MethodWith the wheels CLIWithout
By directorywheels test models&directory=tests.specs.models
One category of manywheels test --filter=controllers&directory=tests.specs.controllers
One spec filewheels test --filter=UserSpec&directory=tests.specs.models.UserSpec
Skip in sourcexdescribe(...) / xit(...) — built into WheelsTestsame

WheelsTest doesn’t ship tag-based include/exclude filtering at this release; directory or single-file filtering plus xdescribe/xit in the spec source is the supported granularity.

  • No Java found (CLI path) — on non-brew installs the CLI refuses to start and points at Adoptium when it can’t find a JVM; set JAVA_HOME in your shell profile. Homebrew installs export it themselves.
  • 404 from /wheels/app/tests — the app isn’t running as development (the allowlist above), or your server isn’t pointing at this app’s public/.
  • Port already in use — a previous wheels start or another server holds the port; lsof -i :<port> and stop it.
  • Playwright JARs missing — browser specs skip with this.browserTestSkipped = true instead of failing, so the suite stays green. Run wheels browser setup once, or see Browser Tests.
  • Populate SQL errors — “Table already exists” means a stale table from a previous run; add DROP TABLE IF EXISTS first. “No such function: NOW” (or similar) means cross-engine SQL drift; use CURRENT_TIMESTAMP — it’s ANSI SQL and works everywhere, while NOW() fails on SQLite and SQL Server.
  • “1 error(s)” with a bundle path but no failing assertion — the runner caught a load-time or setup error in that spec file. The two common causes: it() called directly inside run() without an enclosing describe(), or beforeAll() throwing. Check the spec file at the path shown in the summary.