moonspec

    BDD test framework for MoonBit with Gherkin and Cucumber Expressions

    bdd
    cucumber
    gherkin
    testing
    moonbit
    Download zip
    Author
    Version
    0.5.0
    License
    Apache-2.0
    Last updated
    7 months ago
    Downloads
    30

    #moonspec

    BDD test framework for MoonBit with Gherkin and Cucumber Expressions.

    #Installation

    moon add moonrockz/moonspec

    Add moonrockz/moonspec to the import array in your moon.pkg.json.

    #Quick Start

    Define a World, implement step definitions, and run against inline Gherkin:

    struct CalcWorld { mut result : Int } derive(Default)

    impl @moonspec.World for CalcWorld with configure(self, setup) {
    setup.given0("a calculator", fn() { self.result = 0 })
    setup.when2("I add {int} and {int}", fn(a : Int, b : Int) {
    self.result = a + b
    })
    setup.then1("the result should be {int}", fn(n : Int) {
    assert_eq!(self.result, n)
    })
    }

    async test "Feature: Calculator" {
    let feature =
    #|Feature: Calculator
    #| Scenario: Addition
    #| Given a calculator
    #| When I add 2 and 3
    #| Then the result should be 5
    @moonspec.run_or_fail(CalcWorld::default,
    @moonspec.RunOptions::new([@moonspec.FeatureSource::Text("calc", feature)]),
    )
    }

    Each scenario gets a fresh World via derive(Default). MoonBit structs are reference types -- mutations in closures are visible across step handlers.

    #Features

    • World trait -- per-scenario state with derive(Default)
    • StepLibrary trait -- composable, reusable step groups
    • Cucumber Expressions -- 11 built-in parameter types plus custom types
    • Gherkin -- Feature, Scenario, Scenario Outline, Background, Rules, Data Tables, Doc Strings
    • Lifecycle hooks -- before/after for test run, test case, and test step
    • Tag filtering -- boolean expressions (@smoke and not @slow)
    • Retries -- @retry(N) tags or global config
    • Dry-run -- validate wiring without execution
    • Skip -- @skip / @ignore with optional reason
    • Parallel execution -- bounded concurrency via @async.all()
    • Attachments -- text, binary, or URL on steps and hooks
    • Structured errors -- run_or_fail with snippets and suggestions
    • Codegen -- generate _test.mbt runners from .feature files
    • Formatters -- Pretty, Cucumber Messages (NDJSON), JUnit XML

    #Cucumber Expression Parameters

    ExpressionMoonBit TypeStepValue Variant
    {int}IntIntVal(Int)
    {float}DoubleFloatVal(Double)
    {double}DoubleDoubleVal(Double)
    {long}Int64LongVal(Int64)
    {byte}ByteByteVal(Byte)
    {short}IntShortVal(Int)
    {bigdecimal}@decimal.DecimalBigDecimalVal(@decimal.Decimal)
    {biginteger}BigIntBigIntegerVal(BigInt)
    {string}StringStringVal(String)
    {word}StringWordVal(String)
    {}StringAnonymousVal(String)

    Custom types: setup.add_param_type_strings(name, patterns, transformer?).

    #Step Registration

    Register steps inside World::configure using typed arity-suffixed methods. The numeric suffix indicates how many parameters the handler takes:

    setup.given0("a calculator", fn() { self.result = 0 })
    setup.given1("a user named {string}", fn(name : String) { self.user = name })
    setup.when2("I add {int} and {int}", fn(a : Int, b : Int) { self.result = a + b })
    setup.then1("the result should be {int}", fn(n : Int) { assert_eq!(self.result, n) })
    setup.step0("the system is ready", fn() { () }) // matches any keyword

    The _ctx variants provide access to the full Ctx as the last parameter:

    setup.given1_ctx("a user named {string}", fn(name : String, ctx : Ctx) {
    self.user = name
    self.feature = ctx.scenario().feature_name
    })

    Arities 0--22 are supported for all keywords (given, when, then, step). The original setup.given("pattern", fn(ctx) { ... }) form remains available for advanced use cases.

    #Ctx and StepArg

    Ctx provides indexed access to matched arguments. Each StepArg has value (typed StepValue) and raw (original text). Use struct destructuring: match ctx[0] { { value: IntVal(n), .. } => ... }.

    Other methods: ctx.value(0) returns StepValue directly, ctx.args() returns ArrayView[StepArg], ctx.scenario() returns ScenarioInfo (feature name, scenario name, tags), ctx.step() returns StepInfo (keyword, text).

    #StepLibrary

    Composable step groups via the StepLibrary trait. Returns ArrayView[StepDef]:

    struct AccountSteps { world : BankWorld }

    impl @moonspec.StepLibrary for AccountSteps with steps(self) {
    let defs : Array[@moonspec.StepDef] = [
    @moonspec.StepDef::given1("a balance of {int}", fn(n : Int) {
    self.world.balance = n
    }),
    ]
    defs[:]
    }

    // Compose libraries in World::configure:
    setup.use_library(AccountSteps::new(self))

    #Hooks

    Register lifecycle hooks on Setup. "After" variants receive HookResult:

    setup.before_test_case(fn(ctx) {
    println("Starting: " + ctx.scenario().scenario_name)
    })
    setup.after_test_case(fn(_ctx, result) {
    // result: HookResult::Passed or HookResult::Failed(Array[HookError])
    ignore(result)
    })

    All six: before/after_test_run, before/after_test_case, before/after_test_step.

    #Attachments

    All context types (Ctx, CaseHookCtx, StepHookCtx, RunHookCtx) support attachments:

    ctx.attach("log output", "text/plain")
    ctx.attach_bytes(png_bytes, "image/png", file_name="screenshot.png")
    ctx.attach_url("https://example.com/report", "text/html")

    #RunOptions

    Configure a test run with RunOptions::new(features):

    MethodDefaultDescription
    parallel(Bool)falseEnable parallel scenario execution
    max_concurrent(Int)4Max concurrent scenarios when parallel
    tag_expr(String)""Boolean tag filter expression
    scenario_name(String)""Filter scenarios by name
    retries(Int)0Global retry count for failed scenarios
    dry_run(Bool)falseValidate wiring without execution
    skip_tags(Array[String])["@skip", "@ignore"]Tags that skip scenarios
    add_sink(&MessageSink)--Add a formatter for envelope output
    add_formatter(sink, dest)--Register formatter with output destination
    clear_sinks()--Remove all sinks and formatters

    Feature sources: FeatureSource::Text(uri, content) for inline Gherkin, FeatureSource::File(path) to load from disk.

    #Formatters

    Three built-in formatters (all implement MessageSink):

    • Pretty -- @format.PrettyFormatter::new() -- colored console output
    • JUnit -- @format.JUnitFormatter::new() -- XML for CI
    • Messages -- @format.MessagesFormatter::new() -- Cucumber Messages NDJSON

    Register with a destination:

    options.add_formatter(&@format.PrettyFormatter::new(), @moonspec.Stdout)
    options.add_formatter(&@format.JUnitFormatter::new(), @moonspec.File("report.xml"))

    Output destinations: @moonspec.Stdout, @moonspec.Stderr, @moonspec.File(path).

    When no formatters are configured, defaults to pretty output on stdout.

    #Tag Filtering

    opts.tag_expr("@smoke") // only @smoke
    opts.tag_expr("@smoke and not @slow") // boolean operators
    opts.tag_expr("@smoke or @regression") // either tag

    #Retrying, Dry-Run, Skip

    Retrying -- global or per-scenario @retry(N) tag (overrides global). Each retry creates a fresh World:

    opts.retries(2) // retry failed scenarios up to 2 times

    Dry-run -- validate step wiring without execution. Matched steps report as Skipped("dry run"), undefined steps report with snippets:

    opts.dry_run(true)

    Skip -- scenarios tagged @skip or @ignore are skipped by default. Add a reason with @skip("flaky on CI"). Configure:

    opts.skip_tags(["@skip", "@ignore", "@wip"])

    #Config File

    moonspec.json5 controls codegen and runtime behavior:

    { world: "MyWorld", mode: "per-scenario", // or "per-feature", or per-file map steps: { output: "steps.mbt", exclude: ["features/wip/**"] }, skip_tags: ["@skip", "@ignore"], formatters: [ { type: "pretty", output: "stdout" }, { type: "junit", output: "reports/results.xml" } ] }

    #Packages

    PackageDescription
    moonrockz/moonspecFacade -- World, Setup, Ctx, RunOptions, run, run_or_fail
    moonrockz/moonspec/coreWorld, Setup, HookRegistry, StepRegistry, Ctx
    moonrockz/moonspec/runnerExecutor with tag filtering and parallel support
    moonrockz/moonspec/formatPretty, Messages, JUnit formatters
    moonrockz/moonspec/codegenGenerate test files from Gherkin features
    moonrockz/moonspec/configConfiguration parsing (moonspec.json5)
    moonrockz/moonspec/scannerFeature file discovery and conflict detection

    #Documentation

    Full docs, CLI reference, architecture, and examples: github.com/moonrockz/moonspec

    #License

    Apache-2.0

    Attachable

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    CaseHookCtx

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    Cells

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    Column

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    Columns

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    Ctx

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    DataTable

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    DocString

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    FeatureResult

    Result of executing a feature.

    FeatureSource

    Input source for a feature to be loaded into the cache.

    HookError

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    HookHandler

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    HookRegistry

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    HookResult

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    HookType

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    MoonspecError

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    PendingAttachment

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    RegisteredHook

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    Row

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    Rows

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    RunHookCtx

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    RunOptions

    Options for configuring a test run.

    RunResult

    Complete result of a test run.

    RunSummary

    Summary of an entire run.

    The retried field counts scenarios that required more than one attempt, regardless of their final outcome. A scenario that fails on the first attempt but passes on retry is counted as both passed and retried.

    ScenarioInfo

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    ScenarioResult

    Result of executing a scenario.

    ScenarioStatus

    Aggregate status of a scenario.

    Setup

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepArg

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepDef

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepHookCtx

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepInfo

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepKeyword

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepLibrary

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepMatchResult

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepRegistry

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepResult

    Result of executing a single step.

    StepSource

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    StepStatus

    Status of a single step execution.

    StepValue

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    World

    moonspec — BDD test framework for MoonBit.

    Re-exports core types and runner functions so users import just moonrockz/moonspec and use @moonspec.World, @moonspec.run, etc.

    run

    Run all features and collect results.

    A fresh world is created per scenario via factory() for isolation. When parallel is greater than 0, pickles are executed concurrently using @async.all() with bounded concurrency. Otherwise, pickles run sequentially. Lifecycle hooks registered via Setup are called automatically when present.

    run_or_fail

    async fn[W :
    World
    ] run_or_fail(factory : () -> W, options :
    RunOptions
    ) -> Unit

    Run all features, raising MoonspecError on any failure.

    This is the ergonomic test API. Use in generated tests and manual tests where you want structured error output instead of manual result inspection.

    Source Files