Skip to main content

How to Migrate from livedoc-mocha

This guide walks you through migrating from the legacy livedoc-mocha package to @swedevtools/livedoc-vitest. By the end, you'll have converted your specs to the new API with minimal disruption.

Prerequisites
  • An existing project using livedoc-mocha
  • Node.js 18+
  • Vitest installed (npm install -D vitest)

Overview

The Vitest-based LiveDoc preserves the same Gherkin DSL (feature, scenario, given, when, then) but changes the configuration, imports, context access, and reporter setup. Most spec files require only minor adjustments.

Step 1: Install the New Package

# Remove the old package
npm uninstall livedoc-mocha

# Install the new packages
npm install -D vitest @swedevtools/livedoc-vitest

Step 2: Replace the Configuration

Before (Mocha):

// .mocharc.json
{
"ui": "livedoc-mocha",
"require": ["livedoc-mocha/build/app/index"],
"spec": "**/*.Spec.ts"
}

After (Vitest):

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { LiveDocSpecReporter } from '@swedevtools/livedoc-vitest/reporter';

export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['**/*.Spec.ts'],
reporters: [
new LiveDocSpecReporter({ detailLevel: 'spec+summary+headers' }),
],
},
});

Step 3: Update Imports

Before (Mocha) — globals injected via Mocha UI, no imports needed:

// No imports — feature, scenario, etc. were global
feature("My Feature", () => {
scenario("My Scenario", () => {
given("something", () => { /* ... */ });
});
});

After (Vitest with explicit imports):

import {
feature, scenario, given, when, Then as then, and, but,
} from '@swedevtools/livedoc-vitest';

feature("My Feature", () => {
scenario("My Scenario", () => {
given("something", () => { /* ... */ });
});
});

Or use globals mode to keep zero-import files.

Step 4: Update Context Access

The biggest API change is how you access context objects. In Mocha, contexts were global variables. In Vitest, they are passed as callback parameters.

Feature Context

// ❌ Before (Mocha) — global variable
feature("My Feature", () => {
const title = featureContext.title;
});

// ✅ After (Vitest) — callback parameter
feature("My Feature", (ctx) => {
const title = ctx.feature.title;
});

Scenario Context

// ❌ Before (Mocha)
scenario("My Scenario", () => {
const title = scenarioContext.title;
const givenData = scenarioContext.given;
});

// ✅ After (Vitest)
scenario("My Scenario", (ctx) => {
const title = ctx.scenario.title;
const givenData = ctx.scenario.given;
});

Step Context

// ❌ Before (Mocha)
given("a user with '$1' items", () => {
const count = stepContext.values[0];
const table = stepContext.tableAsEntity;
});

// ✅ After (Vitest)
given("a user with '$1' items", (ctx) => {
const count = ctx.step.values[0];
const table = ctx.step.tableAsEntity;
});

Scenario Outline Context

// ❌ Before (Mocha)
scenarioOutline(`Test
Examples:
| input | expected |
| foo | true |
`, () => {
const input = scenarioOutlineContext.example.input;
});

// ✅ After (Vitest)
scenarioOutline(`Test
Examples:
| input | expected |
| foo | true |
`, (ctx) => {
when("checking <input>", (ctx) => {
const input = ctx.example.input;
});
});

Step 5: Update Reporter Configuration

Before (Mocha) — reporter passed via CLI:

mocha --ui livedoc-mocha --reporter livedoc-mocha/reporter

After (Vitest) — reporter in config:

// vitest.config.ts
reporters: [
new LiveDocSpecReporter({ detailLevel: 'spec+summary+headers' }),
],

Step 6: Update Test Scripts

// package.json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}

Complete API Mapping

Mocha (Before)Vitest (After)Notes
featureContextctx.featurePassed via callback parameter
scenarioContextctx.scenarioPassed via callback parameter
stepContextctx.stepPassed via callback parameter
scenarioOutlineContextctx.exampleAccessed in steps within outline
backgroundContextctx.backgroundPassed via callback parameter
stepContext.values[0]ctx.step.values[0]Same value extraction API
stepContext.tableAsEntityctx.step.tableAsEntitySame table API
stepContext.docStringctx.step.docStringSame doc string API
livedoc.optionslivedoc.optionsSame options singleton
.mocharc.jsonvitest.config.tsConfiguration file
mocha --ui livedoc-mochanpx vitest runTest runner command

Complete Example

Before (Mocha):

feature("Shopping Cart", () => {
scenario("Add item to cart", () => {
given("an empty cart", () => {
scenarioContext.given = { items: [] };
});

when("the user adds 'Widget'", () => {
const item = stepContext.values[0];
scenarioContext.given.items.push(item);
});

then("the cart has '1' item", () => {
const expected = stepContext.values[0];
expect(scenarioContext.given.items.length).to.equal(expected);
});
});
});

After (Vitest):

import {
feature, scenario, given, when, Then as then,
} from '@swedevtools/livedoc-vitest';

feature("Shopping Cart", () => {
scenario("Add item to cart", (ctx) => {
given("an empty cart", (ctx) => {
ctx.scenario.given = { items: [] as string[] };
});

when("the user adds 'Widget'", (ctx) => {
const item = ctx.step.values[0];
ctx.scenario.given.items.push(item);
});

then("the cart has '1' item", (ctx) => {
const expected = ctx.step.values[0];
expect(ctx.scenario.given.items.length).toBe(expected);
});
});
});

Common Variations

Batch Find-and-Replace

For large codebases, use these regex replacements:

FindReplace
featureContext\.ctx.feature.
scenarioContext\.ctx.scenario.
stepContext\.ctx.step.
scenarioOutlineContext\.example\.ctx.example.
backgroundContext\.ctx.background.
\.to\.equal\(.toBe(
\.to\.deep\.equal\(.toEqual(
\.to\.be\.true.toBe(true)
\.to\.be\.false.toBe(false)
caution

Regex replacements need manual review. Ensure each callback receives the ctx parameter — the find-and-replace only updates property access, not function signatures.

Incremental Migration

Migrate one spec file at a time. Vitest and Mocha can coexist temporarily if you maintain both configurations:

  1. Move one .Spec.ts file to Vitest format
  2. Run with npx vitest run -- path/to/File.Spec.ts
  3. Verify output matches expectations
  4. Repeat for the next file

Troubleshooting

ProblemCauseSolution
featureContext is not definedGlobal variables removedAdd ctx parameter to callbacks and use ctx.feature
stepContext is not definedGlobal variables removedAdd ctx parameter to step callbacks and use ctx.step
.to.equal is not a functionChai assertions (Mocha) vs VitestReplace with .toBe(), .toEqual(), etc.
Then import errorESM thenable issueUse Then as then alias
Tests not discoveredFile pattern mismatchEnsure include: ['**/*.Spec.ts'] in vitest config
Async errorsasync on feature/scenarioRemove async from container callbacks — only steps support async