Originally posted on my blog at https://elliotdenolf.com/posts/cucumberjs-with-typescript
Cucumber.js is the JavaScript implementation of Cucumber. The main benefit of writing automated tests for Cucumber is that they are written in plain English, so any non-technical person can read the scenarios and know what is being tested. This is extremely powerful in larger organizations because it allows developers, testers, and business stakeholders to better communicate and collaborate.
This post will go through setting up a basic Cucumber.js suite using TypeScript and cucumber-tsflow. Cucumber-tsflow is a package that will allow us to take advantage of TypeScript’s decorators, which make for clearer step definition code.
The first step will be installing our dependencies:
npm i -D cucumber cucumber-tsflow cucumber-pretty ts-node typescript chai
npm i -D @types/cucumber @types/chai
experimentalDecorators
must also be set to true
in your tsconfig.json
in order for the decorators to compile properly.
The two main components for cucumber tests are feature files and step definitions. Let’s start out by creating a features
directory then creating a file named bank-account.feature
inside it. Our example will be testing the basic functionality of a bank account.
# features/bank-account.feature
Feature: Bank Account
Scenario: Stores money
Given A bank account with starting balance of $100
When $100 is deposited
Then The bank account balance should be $200
This defines a single scenario for depositing money into a bank account. Next, we will create a directory named step-definitions
and create a file named bank-account.steps.ts
within it.
import { binding, given, then, when} from 'cucumber-tsflow';
import { assert } from 'chai';
@binding()
export class BankAccountSteps {
private accountBalance: number = 0;
@given(/A bank account with starting balance of \$(\d*)/)
public givenAnAccountWithStartingBalance(amount: number) {
this.accountBalance = amount;
}
@when(/\$(\d*) is deposited/)
public deposit(amount: number) {
this.accountBalance = Number(this.accountBalance) + Number(amount);
}
@then(/The bank account balance should be \$(\d*)/)
public accountBalanceShouldEqual(expectedAmount: number) {
assert.equal(this.accountBalance, expectedAmount);
}
}
We are utilizing the cucumber-tsflow package which exposes some very useful decorators for our Given
, When
, and Then
steps. The code within each step is fairly simple. The Given
step initializes the accountBalance
, the When
step adds to the balance, and the Then
step asserts its value.
Some specific things to note: this file exports a single class which has the @binding()
decorator on it which is required for cucumber-tsflow to pick up the steps. Each step definition must also have a @given
, @when
or @then
decorator on it. These decorators take a regular expression as a parameter which is how the lines in the feature file map to the code. Also, make note that there are capture groups in the expressions to capture values from the text and are subsequently passed as parameters to the function.
Cucumber is run using the cucumber-js
command with a series of command-line switches. However, this can optionally be put into a cucumber.js
file at the root of the project. Create a cucumber.js
file at the root of the project with the following contents:
// cucumber.js
let common = [
'features/**/*.feature', // Specify our feature files
'--require-module ts-node/register', // Load TypeScript module
'--require step-definitions/**/*.ts', // Load step definitions
'--format progress-bar', // Load custom formatter
'--format node_modules/cucumber-pretty' // Load custom formatter
].join(' ');
module.exports = {
default: common
};
Putting the configuration in this file allows us to simply pass the profile name to cucumber-js
(default
in our case) instead of a long list of arguments. This file is building out all of the command line arguments, joining them, then exporting them under a named property. Let’s add an npm script to our package.json
, so we can easily run it.
// package.json
{
// ...
"scripts": {
"test": "./node_modules/.bin/cucumber-js -p default"
},
// ...
}
The structure of your project should now look like this:
.
|-- cucumber.js
|-- features
| `-- bank-account.feature
|-- package.json
|-- step-definitions
| `-- bank-account.steps.ts
`-- tsconfig.json
Now when we run npm test
, cucumber-js
inside of our node_modules
will be executed with the -p default
switch denoting the default profile exported from our cucumber.js
file we created earlier.
The output should be something similar to this:
Feature: Bank Account
Scenario: Stores money
Given A bank account with starting balance of $100
When $100 is deposited
Then The bank account balance should be $200
1 scenario (1 passed)
3 steps (3 passed)
0m00.004s
That’s it! You’re up and going with Cucumber and TypeScript!
Top comments (8)
Hello, could you pls tell me what is advantage instead of using cucumber scenario? I don´t understand what is plus to use cucumber-tsflow.
We write scenarion in cucumber like -
Then Change user to MO
And I accept the request from private inbox with state xxx
And code look the same as in your example of course with different logic. I can also even create class and make it export to use it on more places so .... I don´t know why to use cucumber-tsflow.
The differences will be how step definitions are written, not the feature files.
The main benefit I see is that it provides type annotations, which allow the code to be extremely clean and understandable. Without these annotations, the code must have a lot of wrapping functions, which make the code harder to re-use. The documentation shows some examples worth looking at.
Why this shows me error telling that declaration expected after @waitForSpinnerToEnd()
@then(/I wait for spinner to end/)
public static waitForSpinner() {
@waitForSpinnerToEnd()
}
export const waitForSpinnerToEnd = async () => {
await World.page.waitFor(200);
await World.page.waitFor(() => !document.querySelector('.spinner-three-bounce'), {visible: true});
};
question regarding Timeouts. In cucumber's steps it is possible to change the timeout per step (read here: github.com/cucumber/cucumber-js/bl...). I cannot figure how it is possible to do so using the cucumber-tsflow :-(
any idea?
Hi can you put JSON and HTML reporter example in this project (Using typescript ?)
The link to the full source is at the bottom of the article. Take a look at the cucumber.js file.
Hi. How would I mock objects with this cucumber setup? Jasmine provides spies out of the box but cucumber does not.
great article.
worth to mention hooks... i.e. Before ==>@before in the Step file