On this page
Config File (moonspec.json5 / moonspec.json)Config FieldsExample: Minimal ConfigExample: Full ConfigRunOptions (Runtime Configuration)Builder MethodsCLI Flagsmoonspec gen testsmoonspec gen stepsmoonspec gen buildmoonspec checkColorsPrecedence OrderMerge BehaviorExampleSchemaConfiguration Reference
moonspec supports configuration through config files, runtime options, and CLI flags. These layers compose: config files set project defaults, CLI flags override them for one-off runs, and RunOptions controls runtime behavior programmatically.
Config File (moonspec.json5 / moonspec.json)
moonspec auto-discovers config files in the current working directory:
| File | Role | Format |
|---|---|---|
moonspec.json | Base | JSON5 |
moonspec.json5 | Override | JSON5 |
Both files are parsed as JSON5, which means you can use comments, trailing commas, and unquoted keys.
When both files exist, moonspec.json serves as the base configuration and moonspec.json5 provides overrides using merge semantics (see Merge Behavior below). When only one file exists, it is used as-is.
To bypass auto-discovery entirely, pass an explicit path with the --config flag:
moonspec gen tests --config path/to/custom.json5 features/*.featureConfig Fields
{
// World type name for codegen (e.g. "CalcWorld")
"world": "CalcWorld",
// Codegen mode: "per-scenario" (default) or "per-feature"
"mode": "per-scenario",
// Skip tags: Gherkin tags that cause scenarios to be skipped
// Default: ["@skip", "@ignore"] (handled by RunOptions at runtime)
"skip_tags": ["@skip", "@ignore", "@wip"],
// Steps codegen configuration for `moonspec gen steps`
"steps": {
"output": "alongside",
"exclude": ["lib/*", "vendor/*"]
},
// Generate tests that print reports without ANSI colors
"no_color": false,
// Test file kind for gen tests / gen build: "wbtest" (default) or "test"
"test_kind": "wbtest"
}world (string)
The World type name used during code generation. This is the struct that holds scenario state and is passed to every step handler.
Required for moonspec gen tests. Can be set here or via the --world CLI flag.
mode (string | object)
Controls how test functions are generated from Gherkin features. Two values are supported:
"per-scenario"(default) -- generates onetestblock per scenario. Each scenario runs independently with its own World instance."per-feature"-- generates onetestblock per feature file. All scenarios in the feature share a single test entry point.
The mode field also accepts a per-file map for mixed-mode projects:
{
"mode": {
"features/checkout.feature": "per-feature",
"features/search.feature": "per-scenario",
"*": "per-scenario" // fallback for all other files
}
}The "*" key acts as a fallback. If a feature file path is not listed and no "*" key exists, the default "per-scenario" mode is used.
skip_tags (array of strings)
Gherkin tags that cause scenarios to be skipped during execution. Scenarios tagged with any of these tags are skipped without executing steps or hooks.
Tags may include a reason using the @skip("reason") syntax. The reason is extracted and attached to the skip status in results.
When omitted from config, the runtime default is ["@skip", "@ignore"].
moonspec gen tests uses the same tags. It puts a #skip("reason") attribute above each generated test whose scenario, feature or Examples block has a skip tag, so moon test reports the scenario as skipped. moon test --include-skipped runs it anyway. In per-feature mode, only a feature-level skip tag adds #skip.
no_color (boolean)
When true, moonspec gen tests generates tests that call options.no_color(true) and create pretty formatters with no_color=true. Default: false. Environment variables can override it at runtime; see Colors.
test_kind (string)
The kind of test file that moonspec gen tests and moonspec gen build write.
"wbtest"(default) -- white-box tests,<name>_feature_wbtest.mbt. They can use private items of the package, such as the World impl thatmoonspec gen stepsgenerates."test"-- black-box tests,<name>_feature_test.mbt. They see only the package's public API, so the World type and its impl must bepub.
An -o value that ends in .mbt is the exact file name, whatever the kind.
steps (object)
Configuration for the moonspec gen steps command.
| Field | Type | Default | Description |
|---|---|---|---|
output | string | "alongside" | Output strategy for generated step registration files |
exclude | array of string | (none) | Glob patterns for files to exclude from scanning |
The output field accepts:
"alongside"(default) -- writes<world>_steps_gen.mbtnext to the step functions."per-package"-- writes onemoonspec_gen.mbtper MoonBit package."generated"-- writes to_generated/<world>_steps.mbtat the module root. That directory is not part of the World type's package, and MoonBit allows the World impl only there, so this output does not compile.gen stepsprints a warning.Custom path -- any other string is treated as a directory path. It must be the directory of the World type's package.
formatters (array of objects)
Configure output formatters for test results. Each entry specifies a formatter type and its output destination.
{
"formatters": [
{ "type": "pretty", "output": "stdout" },
{ "type": "junit", "output": "reports/results.xml" },
{ "type": "messages", "output": "stderr" }
]
}Each formatter entry has:
type--"pretty","junit", or"messages"output--"stdout","stderr", or a file pathno_color-- (optional, pretty only) disable ANSI colors
Default behavior: When no formatters are configured, moonspec outputs pretty-formatted results to stdout.
Multiple formatters: You can configure multiple formatters simultaneously -- for example, pretty output to the console for local development and JUnit XML to a file for CI.
Programmatic API:
let fmt = @format.PrettyFormatter::new()
options.add_formatter(&fmt, @moonspec.Stdout)
let junit = @format.JUnitFormatter::new()
options.add_formatter(&junit, @moonspec.File("reports/results.xml"))
// Remove all formatters
options.clear_sinks()Example: Minimal Config
{
"world": "CalcWorld"
}Example: Full Config
{
"world": "EcomWorld",
"mode": {
"features/checkout.feature": "per-feature",
"*": "per-scenario"
},
"skip_tags": ["@skip", "@ignore", "@wip"],
"steps": {
"output": "alongside",
"exclude": ["vendor/*"]
}
}RunOptions (Runtime Configuration)
RunOptions controls how the runner executes features at runtime. It is created in your test code and passed to the runner.
let opts = RunOptions::new(features)
opts.parallel(true)
opts.max_concurrent(8)
opts.retries(2)
opts.tag_expr("@smoke and not @slow")
opts.skip_tags(["@skip", "@ignore", "@wip"])
opts.dry_run(false)Builder Methods
parallel(value : Bool)
Enable or disable parallel scenario execution. Default: false.
When enabled, scenarios run concurrently up to the max_concurrent limit. Each scenario still gets its own World instance.
max_concurrent(value : Int)
Set the maximum number of scenarios that can run concurrently during parallel execution. Default: 4.
Only takes effect when parallel is true.
retries(value : Int)
Set the global retry count for failed scenarios. Default: 0.
When a scenario fails, it is re-executed up to value additional times. A fresh World instance is created for each attempt. Only the final attempt's result counts toward the run summary.
Per-scenario @retry(N) tags override this global setting. Negative values are clamped to 0.
tag_expr(value : String)
Set a boolean tag expression to filter which scenarios run. Default: "" (no filter).
Supports boolean operators:
"@smoke and not @slow"
"@checkout or @cart"
"not @wip"scenario_name(value : String)
Filter scenarios by name. Default: "" (no filter).
Only scenarios whose name contains the given string will run.
dry_run(value : Bool)
Enable or disable dry-run mode. Default: false.
When enabled, steps are matched against definitions but handlers are not executed. All hooks are skipped. Matched steps report as Skipped("dry run") and undefined steps remain Undefined. Useful for validating step wiring without side effects.
skip_tags(tags : Array[String])
Set the tags that cause scenarios to be skipped. Default: ["@skip", "@ignore"].
Replaces the entire skip tag list. Scenarios with any of these tags are skipped without executing steps or hooks.
no_color(value : Bool)
Turn off ANSI colors in the run_or_fail failure report. Default: false. Environment variables can override it at runtime; see Colors.
add_sink(sink : &MessageSink)
Add a message sink for envelope output. Sinks receive structured messages as the run progresses. Multiple sinks can be added.
add_formatter(sink : &MessageSink, dest : OutputDest)
Register a formatter with an output destination. The formatter receives messages during the run, and its output is automatically written to the destination when the run completes.
Destinations: @moonspec.Stdout, @moonspec.Stderr, @moonspec.File(path).
clear_sinks()
Remove all registered sinks and formatters.
CLI Flags
CLI flags override the corresponding config file fields for the current invocation.
moonspec gen tests
| Flag | Short | Overrides Config | Description |
|---|---|---|---|
--world | -w | world | World type name (e.g. CalcWorld) |
--mode | -m | mode | Codegen mode: per-scenario or per-feature |
--config | -c | (all) | Explicit config file path |
--output-dir | -o | -- | Output directory for generated test files |
--no-color | no_color | Generate tests without ANSI colors | |
--test-kind | test_kind | wbtest (default) or test |
When --world or --mode is provided, the CLI value takes precedence over the config file value. When --config is provided, auto-discovery is bypassed entirely and only the specified file is loaded.
Note: --mode on the CLI applies uniformly to all files in that invocation. To use per-file mode mapping, configure it in the config file instead.
moonspec gen steps
| Flag | Short | Description |
|---|---|---|
--config | -c | Explicit config file path |
--dir | -d | Directory to scan for step definitions |
moonspec gen build
| Flag | Short | Overrides Config | Description |
|---|---|---|---|
--world | -w | world | World type name (e.g. CalcWorld) |
--package | -p | -- | Package directory that gets the tests (default: src) |
--config | -c | (all) | Explicit config file path |
--apply | -- | Write missing entries to <package>/moon.pkg | |
--test-kind | test_kind | wbtest (default) or test | |
--moonx | -- | Rule runs moonx moonrockz/moonspec/cmd/moonspec@<CLI version> instead of moonspec |
moonspec check
The check subcommand accepts positional .feature file arguments and does not use config file settings.
Colors
The run_or_fail failure report and PrettyFormatter use ANSI colors. Turn them off when you build the tests:
moonspec.json5:"no_color": trueCLI:
moonspec gen tests --no-colorCode:
options.no_color(true)or@format.PrettyFormatter::new(no_color=true)
Environment variables override the compiled value when the tests run:
| Variable | Effect |
|---|---|
MOONSPEC_COLOR=always | Color on, even when no_color is set |
MOONSPEC_COLOR=never | Color off |
NO_COLOR=<any non-empty value> | Color off, unless MOONSPEC_COLOR=always (no-color.org) |
Editor test panels that read moon test --test-failure-json may show ANSI codes as raw text. Set NO_COLOR=1 for those runs.
Precedence Order
Configuration values are resolved with the following precedence (highest wins):
CLI flags --
--world,--mode, etc.Override config --
moonspec.json5(during auto-discovery)Base config --
moonspec.json(during auto-discovery)Built-in defaults --
mode: "per-scenario",skip_tags: ["@skip", "@ignore"], etc.
When --config is used, steps 2 and 3 are replaced by the single specified file.
Merge Behavior
When both moonspec.json and moonspec.json5 exist, they are merged using field-level override semantics:
Each top-level field in the override config (
moonspec.json5) replaces the corresponding field in the base config (moonspec.json) when present.Fields present in the base but absent from the override are preserved.
Fields present in the override but absent from the base are added.
For nested objects like
steps, the entire object is replaced -- individual sub-fields are not merged independently.For array fields like
skip_tags, the override array replaces the base array entirely (arrays are not concatenated).
Example
moonspec.json (base):
{
"world": "AppWorld",
"mode": "per-scenario",
"skip_tags": ["@skip", "@ignore"]
}moonspec.json5 (override):
{
"mode": "per-feature",
"skip_tags": ["@skip", "@ignore", "@wip"]
}Resolved config:
{
"world": "AppWorld", // preserved from base
"mode": "per-feature", // replaced by override
"skip_tags": ["@skip", "@ignore", "@wip"] // replaced by override
}Schema
A JSON Schema is available at schemas/moonspec.schema.yaml in the moonspec repository. Use it for IDE validation and autocompletion in editors that support YAML/JSON schema references.
For VS Code, you can add a reference in your workspace settings:
{
"json.schemas": [
{
"fileMatch": ["moonspec.json", "moonspec.json5"],
"url": "./schemas/moonspec.schema.yaml"
}
]
}