# Testing

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

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

```bash
elide test
```

A failing test makes the exit code non-zero.

<img src="https://elide.help/assets/demos/test.gif" alt="Discovering and running JavaScript tests with Elide" width="1591" height="764" loading="lazy" decoding="async" class="rounded-lg border border-border my-6 w-full" />

## 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);
});
```

## 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](https://elide.help/docs/toolchain/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:

```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:

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

JVM tests take JUnit selectors and tag expressions:

```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
```

<img src="https://elide.help/assets/demos/jvm-test.gif" alt="elide test — JUnit selectors on a Kotlin project" width="1591" height="873" loading="lazy" decoding="async" class="rounded-lg border border-border my-6 w-full" />

`--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:

```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() }
}
```

### 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
```

- `# 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

```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" }
  }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `enabled` | Collect without `--coverage`. | `false` |
| `scope` | `"tests"`, `"runs"` or `"all"`; `--coverage` always covers its own task. | `"tests"` |
| `jvm.reports` | `Coverage.htmlReport()`, `xmlReport()`, `csvReport()`. | none |
| `jvm.excludes` | Class patterns (`*`, `?`) not instrumented, such as generated code. | none |
| `js.paths` | Source roots to report; empty means the whole project. | none |

With coverage, `elide run` starts a separate JVM process.

## Options

| Option | Effect |
| --- | --- |
| `-t REGEX` | Match test or enclosing-suite names. |
| `--where CEL` | JavaScript: run tests for which a [CEL](https://elide.help/docs/api-cel) expression over `name`, `parents` and `file` is true. |
| `--select=SELECTOR` | JVM: `class:`, `method:` or `package:` selector; repeatable. |
| `--include-tag=EXPR` | JVM: run tests whose tags match; repeats are ORed. |
| `--exclude-tag=EXPR` | JVM: skip tests whose tags match; repeats are ANDed. |
| `--bail[=N]` | Stop after `N` failures (default 1). |
| `--test-timeout=MS` | Default per-test timeout. |
| `--timeout DURATION` | Time limit for the whole invocation (global). |
| `--only` | Run only tests marked `.only`. |
| `--concurrency=N` | Run files or JVM classes in parallel. Default `1`; `0` is invalid. |
| `--coverage` | Collect coverage; see [Coverage](#coverage). |
| `--reporter=junit` | Write JUnit XML reports. |
| `--reporter=tap` | Write TAP 13 to stdout (and under `--reporter-outfile`). |
| `--reporter-outfile=DIR` | Report directory. |
| `--debugger[=MODE\|ADDRESS]` | Attach a debugger; see below. |
| `--debugger-host=HOST`, `--debugger-port=PORT` | JavaScript debugger address (DAP: 4711; CDP: 1337). |

| `--debugger` form | Debugs |
| --- | --- |
| bare | JVM tests over JDWP on `127.0.0.1:5005`, and JavaScript tests. |
| `=5005`, `=host:5005` | JVM tests only (JDWP). |
| `=dap`, `=cdp` | JavaScript tests only. |

Grammar: [Debugging](https://elide.help/docs/debugging). An unknown option or missing value fails before anything runs
([Build Tasks](https://elide.help/docs/toolchain/build-tasks#options)).

## Limits

- No Python test discovery; run unittest or pytest instead ([Python tests](https://elide.help/docs/python-testing)).
- JVM tests need a manifest.
- Not implemented: `node:test` snapshots, `mock.module`, watch mode, programmatic `run()`, `node:test/reporters`.

Supported API: [Node test API](https://elide.help/docs/api-node-test).

## Samples

- [TypeScript tests](https://elide.help/docs/code-samples#typescript-tests)
- [Kotlin tests](https://elide.help/docs/code-samples#kotlin-tests)
- [Filter tests with CEL](https://elide.help/docs/code-samples#filter-tests-with-cel)
- [JUnit XML in CI](https://elide.help/docs/code-samples#junit-xml-in-ci)

Start from a template:

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