Skip to content

Testing

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

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.

Coverage

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

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()
    }
  }
  js {
    paths { "src" }
  }
}
utf-8 · pkl · Lines: 16
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
js.pathsSource roots to report; empty means the whole project.none

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. For this repository’s Docker-backed integration checks, run make test-testcontainers JVM=yes after a JVM build, or make test-testcontainers against a native build.

Was this page helpful?