Welcome to TSFlow, a lightweight and intuitive framework for defining and running tests in your projects, inspired by SpecFlow and jest-cucumber. TSFlow makes it easy to write, organize, and execute steps in a structured and readable way.
[!NOTE]
This project is still under heavy development. Beware of breaking changes !
- ✏️ Simple syntax: Implement and reuse steps with a clear and concise syntax.
- 🔗 Automatic bindings: Bind your Gherkin scenarios to your step definitions with two lines.
- 💉 Dependency injection: Easily share context between your binding classes.
To install TSFlow, run:
npm install --save-dev @baento/tsflow
[!IMPORTANT]
TSFlow is not a test runner. It currently only runs on Jest, other test runners will be supported in the future (see #44).
Let's assume the following feature file :
# calculator.feature
Feature: Calculator
Scenario: Add two numbers
Given A number 1
And A number 2
When I add the numbers
Then The result is 3
TSFlow allows you to define steps in a declarative way. Here's a simple example to get you started:
// calculator.test.ts
import { loadFeature } from "@baento/tsflow";
import "./calculator.steps.ts";
loadFeature("./calculator.feature");
Steps are defined using the @Step
, @Given
, @When
and/or @Then
decorators on class methods decorated with the @Binding
decorator. They all have the same effect and only exist for readability.
You can pass parameters to your step definitions by defining steps with a Cucumber expression (with placeholders) or alternatively a Regular Expression (with capture groups), parameters will be automatically extracted and passed to your step functions.
[!WARNING]
Using Regular Expressions, parameters will not be automatically casted to the correct type. You might want to add a@Types
decorator onto the method to specify transformers for your parameters.
// calculator.steps.ts
import { Binding, Given, When, Then } from "@baento/tsflow";
@Binding()
class CalculatorSteps {
private numbers: number[] = [];
private result: number = 0;
constructor() {}
@Given("A number {int}")
public stepIs(value: number) {
this.numbers.push(value);
}
@When("I add the numbers")
public stepAdd() {
this.result = this.numbers.reduce((acc, curr) => acc + curr, 0);
}
@Then(/^The result is (\d+)$/)
public stepResult(expectedResult: string) {
expect(this.result).toStrictEqual(Number(expectedResult));
}
}
Classes with matching step definitions will be instanciated for each scenario defined in your feature file. Therefore, in order to share data between binding classes in the same scenario, you can specify dependencies in the @Binding
decorator :
import { Binding, Given } from "@baento/tsflow";
class Calculator {
public numbers: number[] = [];
constructor() {}
}
@Binding([Calculator])
class CalculatorSteps {
constructor(readonly calculator: Calculator) {}
@Given("A number {int}")
public stepIs(value: number) {
this.calculator.numbers.push(value);
}
// ...
}
[!NOTE]
Injected classes will be instanciated and will persist for an entire scenario.
We welcome contributions to TSFlow! If you find a bug or have a feature request, please open an issue on GitHub. You can also fork the repository and submit a pull request.
TSFlow is licensed under the MIT License. See the LICENSE file for more information.
Special thanks to all contributors who have helped make TSFlow possible.
Happy Stepping! 🥒