Skip to content

Testing

Run JavaScript and TypeScript node:test files and JUnit tests with elide test: discovery, selecting tests, options, reports and coverage.

Read offlineelide help tooling-jvm-testing

On this page

elide test runs JavaScript and TypeScript node:test files and JUnit tests.

Terminal
bash
elide test

A failing test makes the exit code non-zero.

Discovering and running JavaScript tests with Elide
 ~/demo  elide test --coverage

test/greet.test.ts
✓ greets by name
✓ greets the world
╭Coverage Report─────────────────────────────╮
│ TypeScript · 1 file                        │
│   Statements ━━━━━━━━━━━━━━━━━━━━ 6/6 100% │
│        Lines ━━━━━━━━━━━━━━━━━━━━ 1/1 100% │
│    Functions ━━━━━━━━━━━━━━━━━━━━ 4/4 100% │
╰────────────────────────────────────────────╯

 2 pass
 0 skip
 0 fail
Ran 2 tests across one file. (0.07s)
 


JavaScript and TypeScript

Tests use the node:test API and need no manifest. A file is a test if it matches *.test.*, *.spec.*, *_test.* or *_spec.*, or is under test, tests or __tests__. Extensions: .js, .mjs, .cjs, .ts, .mts, .cts.

Discovery skips hidden, dependency and build-output directories. It stops at any nested project (a directory with elide.pkl, package.json, pom.xml or requirements.txt), so a workspace root finds no member tests. Name a path to run them.

js
import test from "node:test";
import assert from "node:assert";

test("adds two values", () => {
  assert.equal(1 + 1, 2);
});
utf-8 · js · Lines: 6

JVM tests

JVM tests run on the JUnit Platform and need an elide.pkl. src/test is the default test source set; a declared source set of type test replaces it (Source Sets).

  • Test sets get testPackages plus the main set’s dependencies.
  • Bundled JUnit libraries are added by default. jvm.features.testing = false removes both the JUnit compile APIs and the bundled runtime; a JVM test run then needs a JUnit console launcher (junit-platform-console) in testPackages, and fails with an error otherwise. Runs that include no JVM tests are unaffected.
  • processors run on test sources.
  • The test JVM gets jvm.flags and jvm.defs (as -D).
  • modules dependencies of the test and main sets go on --module-path with --add-modules=ALL-MODULE-PATH.
  • Parallel settings in a junit-platform.properties on the test classpath override --concurrency.

Select tests

Paths, directories or globs narrow JavaScript and TypeScript tests:

Terminal
bash
elide test src/api
elide test src/api/client.test.ts

Path arguments skip JVM tests and print Path filtering unsupported for JVM tests: <paths>. -t matches test names in both runners:

Terminal
bash
elide test -t 'parses .+ headers'

JVM tests take JUnit selectors and tag expressions:

Terminal
bash
elide test --select=class:com.example.ParserTest
elide test --select=method:com.example.ParserTest#parsesHeaders
elide test --select=package:com.example.parser   # the package and its subpackages
elide test com.example.parser                     # a dotted bare token is a package selector
elide test --include-tag=fast --include-tag=unit  # fast OR unit
elide test --exclude-tag=slow --exclude-tag=flaky # neither slow nor flaky
elide build test --select=class:com.example.ParserTest
elide test — JUnit selectors on a Kotlin project
 ~/demo  elide test --select class:calc.CalculatorTest

calc.CalculatorTest
✓ divides evenly() [13ms]
✓ adds two numbers() [3ms]
✓ rejects division by zero() [19ms]


 3 pass
 0 skip
 0 fail
Ran 3 tests across one file. (0.63s)
 ~/demo  elide test --select 'method:calc.FormatTest#formats cents as dollars'

calc.FormatTest
✓ formats cents as dollars() [23ms]


 1 pass
 0 skip
 0 fail
Ran one test across one file. (0.59s)
 

--include-tag='slow & !flaky' is one expression. Tag filters apply after selectors.

Output and reports

Progress goes to stderr; the final tally goes to stdout:

Terminal
bash
elide test > summary.txt
elide test 2> progress.txt

Reporters write to .dev/reports/tests/<language>/<source-set>/. testing.reports in elide.pkl turns them on without a flag:

pkl
import "elide:Testing.pkl" as Testing

testing {
  reports { Testing.xmlReport() }
}
utf-8 · pkl · Lines: 5

TAP output

--reporter=tap makes stdout a pure TAP 13 stream:

text
TAP version 13
# start 1: suite A > t1
# out 1: printed from inside a test
ok 1 - suite A > t1
# start 2: broken
not ok 2 - broken
  ---
  message: "expected 1 to equal 2"
  severity: "ERR_ASSERTION"
  ...
1..2
utf-8 · text · Lines: 11
  • # start <id> opens a test; its output lines are # out <id>: <text>. # out: <text> belongs to no test.
  • Test points are numbered 1..N in finishing order. Match points to tests by the # start line, not the number.
  • A YAML block appears only on failures with details; severity is the runner’s error code, if any.
  • Stderr is not framed: elide test --reporter=tap 2>&1 is not valid TAP.
  • JVM: output is tagged with its test only while exactly one test runs. With --concurrency above 1 or a project junit-platform.properties, it is untagged.
  • JavaScript: console.log inside a test, or at top level after importing node:test, is framed. Output before that import is raw. Hook output belongs to the test the hook wraps; t.diagnostic is framed as # out.

Set ELIDE_TEST_METADATA=1 with --reporter=tap to include versioned # elide-test: JSON comments before results. These carry the result number, start identity, engine, source file, suite, status and measured duration in milliseconds. Start identity connects concurrent tests to their captured output, even when names repeat and completion order differs. Ordinary TAP consumers ignore these comments; default output and per-suite reports remain unchanged.

Coverage

Terminal
bash
elide test --coverage
elide run --coverage

In a Tern project workspace, choose Test, then Run with coverage. The Coverage tab appears once the run is measured: it lists the measured files with their covered, partly covered and uncovered lines, and shows the selected file’s current source with each measured line tinted by its coverage. Lines the runtime never measures (closing braces, blank lines, comments) take a lighter tint when the block they belong to ran: the nearest measured line above them in that block was covered and no uncovered line comes between. They are not counted in the percentages. Next uncovered and Previous uncovered (n and p) move between gaps, and Open line N in Elide source opens the file at the line in focus; save your changes and choose Run again to measure the updated code. Run history keeps previous measurements available, and unavailable branch data is shown separately from zero coverage.

The open buttons in the Tests view (Open in Elide source, and Open on a test’s card) respond to a click only. A terminal program drawing a Tern view cannot ask Tern to open a link from a key, so Enter does not open the selection; click the button, or ⌘-click a linked file name. See Coverage in Tern.

Test integrations can set ELIDE_COVERAGE_REPORT to a run-specific file path when requesting coverage. Elide atomically writes a versioned JSON artifact with measured source files, executable-line coverage and branch counters where available. This artifact is independent of configured LCOV or JaCoCo report files. An integration should allocate a new destination for each run.

JavaScript source fingerprints describe the text evaluated by the runtime, even if the file changes before the report is written. Coverage is a measurement of that source version. A missing fingerprint or branch measurement means unavailable data; it does not mean that the current file matches or that no branches executed. Line coverage records whether a line was covered, not how many times it ran.

JVM coverage goes to .dev/reports/coverage/jvm/<task>/ as HTML, XML or CSV; JavaScript and TypeScript coverage is LCOV, in .dev/reports/coverage/js/lcov.info for tests and .dev/reports/coverage/js/run/lcov.info for runs. The console prints one combined summary. The top-level coverage block collects without the flag:

pkl
import "elide:Coverage.pkl" as Coverage

coverage {
  enabled = true
  scope = "tests"
  jvm {
    excludes { "org.example.generated.*" }
    reports {
      Coverage.xmlReport()
      Coverage.htmlReport()
    }
    minimum {
      line = 0.85
      branch = 0.70
    }
  }
  js {
    paths { "src" }
    minimum { line = 0.80 }
  }
}
utf-8 · pkl · Lines: 21
PropertyMeaningDefault
enabledCollect without --coverage.false
scope"tests", "runs" or "all"; --coverage always covers its own task."tests"
jvm.reportsCoverage.htmlReport(), xmlReport(), csvReport().none
jvm.excludesClass patterns (*, ?) not instrumented, such as generated code.none
jvm.minimum.line, jvm.minimum.branchMinimum covered ratio, 0.0 to 1.0.none
js.pathsSource roots to report; empty means the whole project.none
js.minimum.lineMinimum covered line ratio, 0.0 to 1.0.none

When a run collects coverage, elide test exits non-zero if a minimum is not met, naming the property, the required ratio and the measured one. A minimum with nothing to measure, such as no JVM tests or no analyzed classes, also fails. Reports are written either way. JVM ratios count every class the test run analyzes.

With coverage, elide run starts a separate JVM process.

Options

OptionEffect
-t REGEXMatch test or enclosing-suite names.
--where CELJavaScript: run tests for which a CEL expression over name, parents and file is true.
--select=SELECTORJVM: class:, method: or package: selector; repeatable.
--include-tag=EXPRJVM: run tests whose tags match; repeats are ORed.
--exclude-tag=EXPRJVM: skip tests whose tags match; repeats are ANDed.
--bail[=N]Stop after N failures (default 1).
--test-timeout=MSDefault per-test timeout.
--timeout DURATIONTime limit for the whole invocation (global).
--onlyRun only tests marked .only.
--concurrency=NRun files or JVM classes in parallel. Default 1; 0 is invalid.
--coverageCollect coverage; see Coverage.
--reporter=junitWrite JUnit XML reports.
--reporter=tapWrite TAP 13 to stdout (and under --reporter-outfile).
--reporter-outfile=DIRReport directory.
--debugger[=MODE|ADDRESS]Attach a debugger; see below.
--debugger-host=HOST, --debugger-port=PORTJavaScript debugger address (DAP: 4711; CDP: 1337).
--debugger formDebugs
bareJVM tests over JDWP on 127.0.0.1:5005, and JavaScript tests.
=5005, =host:5005JVM tests only (JDWP).
=dap, =cdpJavaScript tests only.

Grammar: Debugging. An unknown option or missing value fails before anything runs (Build Tasks).

Limits

  • No Python test discovery; run unittest or pytest instead (Python tests).
  • JVM tests need a manifest.
  • Not implemented: node:test snapshots, mock.module, watch mode, programmatic run(), node:test/reporters.

Supported API: Node test API.

Samples

Start from a template:

Terminal
bash
elide init --template java        # Java app with a JUnit test
elide init --template ktjvm       # Kotlin app with a test

Shared test containers

Declare disposable services in testing.testcontainers. Elide starts them before the project’s selected JavaScript, TypeScript, and JUnit runners, waits for readiness, and stops them when the test run finishes. Every runner in that project receives the same connection values. This works independently of Micronaut or any other application framework.

pkl
amends "elide:project.pkl"
name = "database-tests"

testing {
  testcontainers {
    containers {
      ["postgres"] {
        image = "postgres:17-alpine"
        environment {
          ["POSTGRES_USER"] = "test"
          ["POSTGRES_PASSWORD"] = "test"
          ["POSTGRES_DB"] = "app"
        }
        ports { 5432 }
        wait { timeoutSeconds = 90 }
      }
    }
    environment {
      ["DATABASE_HOST"] = "${postgres.host}"
      ["DATABASE_PORT"] = "${postgres.port.5432}"
      ["JDBC_URL"] = "jdbc:postgresql://${postgres.host}:${postgres.port.5432}/app"
    }
  }
}
utf-8 · pkl · Lines: 24

Read these values through process.env in JavaScript/TypeScript or System.getenv in Java/Kotlin. Bindings override inherited environment variables for the test runners only. A binding can include ${name.host}, ${name.port.CONTAINER_PORT}, or ${name.id}; unknown references fail before startup. Ports are TCP ports inside the container. Testcontainers assigns available host ports, so concurrent runs do not need fixed port reservations. Workspace members have independent container sets.

Each container supports image, an optional command argument list, environment, ports, and wait:

Wait strategyRequired settingsBehavior
"port" (default)Nonempty portsWait for exposed ports to listen
"http"wait.port naming an exposed portGET wait.path (default /), expecting wait.status (default 200)
"log"wait.patternWait for one matching Java regular expression in container logs

wait.timeoutSeconds defaults to 60. The overall startupTimeoutSeconds defaults to 300 and includes image downloads and all readiness checks. Startup failures fail the test run. Container command arguments are passed directly; use an explicit shell command inside the container if needed.

A Docker-compatible daemon and JDK 17 or newer with source-file launching support are required. Elide uses its configured JVM toolchain and resolves Testcontainers separately from application dependencies on first use. Docker connection settings such as DOCKER_HOST are inherited. This path is also used by the native Elide CLI; Testcontainers runs in a helper JVM. It adds nothing to your application JAR or Native Image.

elide install and compilation do not start containers. elide test retains normal test selection, reporting, and coverage behavior; elide build test supports containers for JVM test tasks too. Cleanup runs after success, failure, or cancellation. The helper monitors its parent connection, and Testcontainers’ resource reaper handles orphaned Docker resources. Keep the resource reaper enabled in CI.

Session files live under .dev/testcontainers/. Requests containing container environment values are deleted after the helper reads them and again during cleanup. Helper logs remain there for diagnosis; they can contain output from the container library. On POSIX systems, session directories are private.

The testcontainers project sample contains JavaScript and JUnit tests against one shared HTTP container. Run it with elide test.

Was this page helpful?