@deepracticex/vitest-cucumber
v1.4.3
Published
Runtime API for Cucumber BDD step definitions and hooks
Downloads
77
Readme
@deepracticex/vitest-cucumber
Native Cucumber BDD experience for Vitest - Write standard Gherkin features with authentic Cucumber APIs.
✨ 100% Cucumber-compliant - Standard execution order, familiar APIs, no compromises
🚀 Zero configuration - Works with Vitest out of the box
📝 Real Gherkin - Use .feature files, not wrapped test code
🎯 Type-safe - Full TypeScript support with type inference
Note: This package provides the runtime API. You need
@deepracticex/vitest-cucumber-pluginfor transforming.featurefiles.
What's This Package?
This is the runtime library that provides:
Given,When,Then- Step definition APIsBefore,After,BeforeAll,AfterAll- Lifecycle hooksDataTable- Rich API for tabular datasetWorldConstructor- Custom test context
The generated test code (from .feature files) imports from this package.
Installation
# Install the package
pnpm add -D @deepracticex/vitest-cucumber vitest
# Or use npm
npm install -D @deepracticex/vitest-cucumber vitestArchitecture
┌─────────────────────────────────────┐
│ @deepracticex/vitest-cucumber │
│ │
│ • Given/When/Then (main export) │
│ • vitestCucumber (/plugin) │
│ • Runtime APIs (/runtime) │
└─────────────────────────────────────┘
One package, everything included.Quick Start
1. Configure Plugin
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { vitestCucumber } from '@deepracticex/vitest-cucumber/plugin';
export default defineConfig({
plugins: [
vitestCucumber({
// Feature files location
features: ['features/**/*.feature'],
// Step definitions directory
steps: 'tests/e2e/steps',
// Support files (optional - auto-discovered if not specified)
// support: 'tests/e2e/support',
}),
],
test: {
include: ['**/*.feature'],
},
});2. Write Feature
# features/calculator.feature
Feature: Calculator
Scenario: Add two numbers
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 1203. Define Steps (This Package)
// tests/steps/calculator.steps.ts
import { Given, When, Then } from '@deepracticex/vitest-cucumber';
import { expect } from 'vitest';
Given('I have entered {int} into the calculator', function (num: number) {
this.numbers = this.numbers || [];
this.numbers.push(num);
});
When('I press add', function () {
this.result = this.numbers.reduce((a, b) => a + b, 0);
});
Then('the result should be {int}', function (expected: number) {
expect(this.result).toBe(expected);
});4. Run Tests
pnpm vitestCore APIs
Step Definitions
import { Given, When, Then, And, But } from '@deepracticex/vitest-cucumber';
// Parameter types: {int}, {float}, {string}, {word}
Given('I have {int} items', function (count: number) {
this.count = count;
});
When('I add {int} more', function (more: number) {
this.count += more;
});
Then('I should have {int} items', function (expected: number) {
expect(this.count).toBe(expected);
});Hooks
Cucumber-standard execution order (verified against official spec):
import {
Before,
After,
BeforeAll,
AfterAll,
} from '@deepracticex/vitest-cucumber';
BeforeAll(async function () {
// Runs once before all scenarios
});
Before(async function () {
// Runs before each scenario (and before Background steps)
// Perfect for cleanup/reset operations
});
After(async function () {
// Runs after each scenario
});
AfterAll(async function () {
// Runs once after all scenarios
});Execution order per scenario:
BeforehooksBackgroundsteps (if defined)- Scenario steps
Afterhooks
This follows the official Cucumber specification, ensuring your tests behave identically to other Cucumber implementations.
Custom World Context
Define a custom World class to share state across steps (standard Cucumber.js pattern):
import { setWorldConstructor } from '@deepracticex/vitest-cucumber';
// Define World as a class (recommended - standard Cucumber.js)
class CustomWorld {
calculator: Calculator;
result: number;
constructor() {
this.calculator = new Calculator();
this.result = 0;
}
// Add custom helper methods
async performCalculation() {
// ...
}
}
setWorldConstructor(CustomWorld);
// Use in steps with type safety
Given('...', function (this: CustomWorld) {
this.calculator.add(5);
});Alternative: Factory function pattern
setWorldConstructor(() => ({
calculator: new Calculator(),
result: 0,
}));Both patterns work - use whichever fits your style. Class pattern provides better IDE support and this binding.
Project Structure
Recommended Structure (Cucumber.js Compatible)
project/
├── features/ # Feature files
│ ├── auth.feature
│ └── user.feature
└── tests/
└── e2e/
├── support/ # Loaded FIRST (hooks, world, custom types)
│ ├── hooks.ts
│ └── world.ts
└── steps/ # Loaded SECOND (step definitions)
├── auth.steps.ts
└── user.steps.tsConfiguration:
vitestCucumber({
features: ['features/**/*.feature'],
steps: 'tests/e2e/steps',
});Support Directory Auto-Loading
Files in support/ directories are automatically loaded before step definitions, ensuring hooks and world setup are available when steps execute.
How Auto-Discovery Works
If you don't specify support in the config, the plugin intelligently searches for support files:
Smart detection - Checks sibling directory of steps:
steps='tests/bdd/steps'→ auto-findstests/bdd/supportsteps='tests/e2e/steps'→ auto-findstests/e2e/supportsteps='src/test/steps'→ auto-findssrc/test/support
Common fallback locations:
tests/e2e/support/**/*.tstests/support/**/*.tstests/bdd/support/**/*.ts
Loading order is always:
- Support files (hooks, world, custom types)
- Step definitions
Explicit Configuration
You can also explicitly specify support directories:
vitestCucumber({
steps: 'tests/steps',
support: 'tests/support', // Single directory
});
// Or multiple directories
vitestCucumber({
steps: 'tests/steps',
support: ['tests/support', 'src/test/fixtures'], // Multiple
});Example support/hooks.ts:
import { Before, After } from '@deepracticex/vitest-cucumber';
Before(function () {
// Initialize context before each scenario
this.services = createTestServices();
});
After(function () {
// Clean up after each scenario
this.services?.cleanup();
});Example support/world.ts:
import { setWorldConstructor } from '@deepracticex/vitest-cucumber';
// Standard Cucumber.js class pattern
class CustomWorld {
services: any;
userData: Record<string, any>;
constructor() {
this.services = null;
this.userData = {};
}
// Custom helper methods
async login(username: string) {
// Implementation...
}
async setupTestData() {
// Implementation...
}
}
setWorldConstructor(CustomWorld);Alternative Structures
Features co-located with source:
src/
├── auth/
│ ├── auth.feature
│ └── auth.steps.ts
└── user/
├── user.feature
└── user.steps.tsEverything in tests:
tests/
├── features/
│ └── *.feature
├── support/
│ └── hooks.ts
└── steps/
└── *.steps.tsConfiguration:
vitestCucumber({
features: ['tests/features/**/*.feature'],
steps: 'tests/steps',
});Data Tables
Scenario: Multiple users
Given the following users:
| name | email | role |
| Alice | [email protected] | admin |
| Bob | [email protected] | user |import { Given, DataTable } from '@deepracticex/vitest-cucumber';
Given('the following users:', function (table: DataTable) {
// Get as array of objects
const users = table.hashes();
users.forEach((user) => {
console.log(user.name, user.email, user.role);
});
// Or raw 2D array
const rows = table.raw();
});Doc Strings
Scenario: Process JSON
Given I have the following JSON:
"""json
{
"name": "Test",
"value": 42
}
"""Given('I have the following JSON:', function (docString: string) {
this.data = JSON.parse(docString);
});For Package Authors
If you're building a testing utilities wrapper:
// your-package/cucumber.ts
export * from '@deepracticex/vitest-cucumber';
// Export runtime for generated code
export * from '@deepracticex/vitest-cucumber/runtime';Then configure the plugin to use your package:
// vitest.config.ts
vitestCucumber({
runtimeModule: '@your-company/testing-utils',
});Generated code will import from your package instead:
import { StepExecutor, ... } from '@your-company/testing-utils/runtime';Why Choose This Over Alternatives?
🎯 Authentic Cucumber Experience
Unlike test wrappers that mimic Gherkin syntax, we provide:
- Real
.featurefiles parsed with official@cucumber/gherkin - Standard Cucumber APIs -
Given,When,Then,Before,After - Spec-compliant execution order - Hooks run exactly as documented in Cucumber spec
- Support directory convention - Automatically loads support files before steps (Cucumber.js compatible)
- Familiar patterns - If you know Cucumber, you already know this
⚡ Vitest Native
- Built for Vitest from the ground up
- No extra test runners or frameworks
- Full compatibility with Vitest ecosystem (UI, coverage, watch mode)
- TypeScript support with proper type inference
🔒 Production Ready
- Comprehensive test coverage
- Actively maintained
- Used in production applications
- Regular updates following Vitest and Cucumber standards
Requirements
- Node.js >= 18
- Vitest >= 2.0 || >= 3.0
License
MIT
