Yadda brings true BDD to JavaScript test frameworks such as node:test, Mocha and Jasmine. By true BDD we mean that the ordinary language (e.g. English) steps are mapped to code, rather than merely decorating it. This matters because just like comments the decorative steps used by tools such as Jasmine and Mocha can fall out of date and are a form of duplication.
- Mature and stable. Yadda has been around since 2012 and its API has long since settled. It's battle-tested in real-world suites.
- Zero dependencies. Yadda installs nothing else into your
node_modules. No transitive supply chain, no version conflicts, less maintenance. - Small, well-factored codebase. Just over 2,000 lines of source (excluding tests and examples), built from deliberately tiny functions averaging a few lines each — mostly the feature parser and runner plugins, atop a smaller interpreter core. It is easy to read, easy to reason about, and easy to extend.
- High test coverage. Covered by ~200 meaningful tests at ~87% line / ~98% branch coverage, run against current LTS and current Node.js releases.
- True BDD. Steps map to real functions, so your specifications cannot silently drift away from the behaviour they describe.
- Markdown feature files. Write specifications as GitHub-flavoured markdown so they render beautifully on GitHub, with headings for features and scenarios, list items for steps, fenced blocks for doc-strings, and markdown tables for data. See Markdown Feature Files.
- Unopinionated. Yadda is not a test runner or a test framework — its one job is to map lines of text to function calls. It plugs into the runner you already use.
Yadda's BDD implementation is like Cucumber's in that it maps ordinary language steps to code. You could of course use CucumberJS, but we find Yadda less invasive, and prefer its flexible syntax to Gherkin's rigidity.
| Yadda | CucumberJS | |
|---|---|---|
| Dependencies | 0 | 30+ transitive dependencies |
| Source size | ~2,000 lines (zero deps) | Substantially larger, plus its dependency tree |
| Runner | Bring your own (node:test, Mocha, Jasmine, …) | Ships its own runner |
| Language syntax | Flexible — steps need not follow rigid Given/When/Then | Gherkin only |
| Step conflicts | Reduced via dynamic library selection and dictionaries | More prone to clashes across a large step catalogue |
| Step data | Dictionaries can source values (e.g. from a remote system), not just match literals | Literal capture groups |
Three things in particular set Yadda apart:
- Dynamic library selection. You can compose different step libraries per scenario, so the same phrase can mean different things in different contexts without conflicting. See Step Libraries.
- Dictionaries. Named, reusable terms that both reduce step conflicts and let step parameters be converted or sourced — turning matched text into integers, dates, parsed tables, or even entities fetched from a remote system. See Dictionaries.
- A more flexible language. Steps do not have to start with Given/When/Then, terms can be aliased, and localisation is built in for many languages. See Feature Files and Localisation.
npm install --save-dev yadda
Yadda 3.0 is Node-only and requires Node.js >= 20. In-browser bundles were removed in 3.0 — see Migrating to 3.0.
.
├── test.js
├── lib
│ └── Wall.js
└── test
├── features
│ └── bottles.feature
└── steps
└── bottles-library.js
test/features/bottles.feature
Feature: 100 Green Bottles
Scenario: Should fall from the wall
Given 100 green bottles are standing on the wall
When 1 green bottle accidentally falls
Then there are 99 green bottles standing on the wall
test/steps/bottles-library.js
import assert from 'node:assert';
import Yadda from 'yadda';
import Wall from '../../lib/Wall.js';
const { ContextParamLibrary, localisation: { English } } = Yadda;
const library = English.localise(new ContextParamLibrary());
export default library
.given('$NUM green bottles are standing on the wall', (ctx, number) => {
ctx.wall = new Wall(Number(number));
})
.when('$NUM green bottle accidentally falls', (ctx, number) => {
ctx.wall.fall(Number(number));
})
.then('there are $NUM green bottles standing on the wall', (ctx, number) => {
assert.equal(Number(number), ctx.wall.bottles);
});test.js
import Yadda from 'yadda';
import library from './test/steps/bottles-library.js';
const { plugins: { nodetest }, FeatureFileSearch, createInstance } = Yadda;
const { featureFile, scenarios, steps } = nodetest.StepLevelPlugin.init();
new FeatureFileSearch('./test/features').each((file) => {
featureFile(file, (feature) => {
const yadda = createInstance(library);
scenarios(feature.scenarios, (scenario) => {
const ctx = {};
steps(scenario.steps, (step, done) => {
yadda.run(step, ctx, done);
});
});
});
});lib/Wall.js
export default class Wall {
constructor(bottles) {
this.bottles = bottles;
}
fall(n) {
this.bottles -= n;
}
}node --test
- Getting Started — installation, the interpreter, and your first suite
- Step Libraries — mapping text to functions, sync/async/promise steps, aliases, dynamic selection
- Dictionaries — reusable terms, converters, and sourcing step data
- Managing State — sharing context across steps and libraries
- Localisation — writing features in other languages
- Events — hooking into scenario/step/execute events for debugging
- Feature Files — the full feature-file syntax reference
- Plugins — integrating with node:test, Mocha and Jasmine
- API Reference — the public API surface
- Migrating to 3.0 — breaking changes from 2.x
The examples directory demonstrates every key feature and how to integrate Yadda with common test runners. To run one:
git clone https://github.com/acuminous/yadda.git
cd yadda
npm install
npm link
cd examples/<desired-example>
npm install
npm test
Pull requests are welcome. Please read CONTRIBUTORS.md first — it describes the principles and conventions that keep the Yadda codebase small and consistent.