Step-by-Step Guide: Testing TypeScript/JavaScript Files with Cucumber.js

Testing TypeScript/JavaScript Files with Cucumber.js: A Step-by-Step Guide

what is cucumber:

It's a widely used open-source tool for behavior-driven development (BDD). It allows developers, testers, and other stakeholders to collaborate and define the behavior of a software application in a human-readable format. Cucumber helps bridge the gap between technical and non-technical team members by providing a common language to describe and understand software requirements and features.

features:

  1. Behavior-Driven Development (BDD): Cucumber promotes BDD principles, focusing on the behavior and functionality of the software rather than just code implementation. Scenarios are written in a natural language format that is easy to understand by both technical and non-technical stakeholders.

  2. Gherkin Syntax: Scenarios in Cucumber are written using the Gherkin syntax, which is a plain-text language with specific keywords. Gherkin syntax provides a structured way to define features, scenarios, steps, and expected outcomes.

  3. Collaboration: Cucumber scenarios are often authored collaboratively by developers, testers, product owners, and business analysts. This collaboration ensures that everyone involved understands the expected behavior of the software.

  4. Reusable Step Definitions: Cucumber allows you to write step definitions in code that correspond to the Gherkin steps in feature files. These step definitions map the plain text steps to executable code, enabling automation of test scenarios.

  5. Automation: Cucumber supports the automation of test scenarios by linking Gherkin steps to actual code that interacts with the application. This makes it possible to execute and verify scenarios automatically.

  6. Multiple Language Support: Cucumber supports multiple programming languages, allowing you to write step definitions in the programming language of your choice, such as Java, JavaScript, Ruby, Python, and more.

  7. Test Reporting: Cucumber provides test reporting capabilities that help teams track the status of tests, identify failures, and analyze test results. This reporting can be useful for monitoring the health of your application and ensuring its quality.

  8. Extensibility: Cucumber offers a rich ecosystem of plugins and integrations that enhance its capabilities. This includes integration with testing frameworks, continuous integration tools, and reporting tools.

Usage:

We'll explore how to perform testing using Cucumber in JavaScript/TypeScript.

To begin, let's start by creating a new project directory:

mkdir cucumber-ts-example
cd cucumber-ts-example

Initialize your project with npm (Node Package Manager):

npm init -y

Add the test script into your package.json file

"scripts": {
    "test": "cucumber-js --require-module ts-node/register 'test/features/**/*.feature' --require 'test/step_definitions/**/*.ts'"
    }

Install Dependencies:

npm install --save @cucumber/cucumber chai ts-node typescript

Create a tsconfig.json file in the project directory to configure TypeScript and add the below code in it.

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES6",
    "outDir": "./dist"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

make your folder structure looks like this

project-root/
│
├── features/
│   ├── task.feature
│
├── step_definitions/
│   ├── taskSteps.ts
│
├── src/
│   ├── index.ts
│
├── tsconfig.json
├── package.json

You'll notice that we have a "features" folder containing files with the ".feature" extension. Additionally, there's a "step_definitions" directory with a "taskSteps.ts" file inside. Keep in mind that you're free to name these folders and files as you prefer. However, the crucial file to have is the one with the ".feature" extension.

what is a feature file:

A feature file is a text-based document used in Behavior-Driven Development (BDD) to define the behavior of a software feature. It's written in a natural language format called Gherkin, which is easy to read and understand by both technical and non-technical stakeholders. Feature files serve as a bridge between business requirements and technical implementation.

The purpose of a feature file is to describe the behavior or functionality of a software feature in a structured manner. It's a collaboration tool that brings together various stakeholders, including business analysts, developers, testers, and product owners, to define and understand what the software should do.

A feature file is divided into sections:

  • Feature: Describes the high-level functionality or business feature that is being tested. It provides context for the scenarios within the file.

  • Scenario: Represents a single test case. It describes a specific instance of how the feature should behave. Each scenario consists of steps.

  • Given-When-Then Steps: These are the building blocks of scenarios. Each step has a specific role:

    • Given: Describes the initial context or setup for the scenario.

    • When: Describes the action or event that triggers the behavior.

    • Then: Describes the expected outcome or result of the scenario.

What is Step Definitions:

Step definitions are code implementations that map the Gherkin steps in feature files to actual code. They define what actions to take when each step is encountered during test execution. Step definitions bridge the gap between the human-readable feature files and the technical implementation.

For each Gherkin step in a feature file, there must be a corresponding step definition. These step definitions are typically organized into separate files or modules for better maintainability.

When Cucumber executes a scenario, it reads the steps from the feature file and matches them to the corresponding step definitions based on the keywords (Given, When, Then). Cucumber then executes the code associated with each step definition, allowing the scenario to interact with the application.

Example:

Let's set up a script file to manage your Cucumber test cases for two scenarios: adding numbers and retrieving user information. Assuming you've organized your project with a "features" folder containing ".feature" files and a "step_definitions" directory with the "taskSteps.ts" file, let's create a script using TypeScript.

export interface IUser {
  name: String;
  age: String;
}
const users: IUser[] = [
  {
    name: "User 1",
    age: "5 years",
  },
  {
    name: "User 2",
    age: "10 years",
  },
  {
    name: "User 20",
    age: "100 years",
  },
];

class Action {
  add(numbers: number[]) {
    return numbers.reduce((sum, num) => sum + num, 0);
  }

  getUseName(userName: String): IUser | undefined {
    return users.find((user) => {
      return user.name === userName;
    });
  }
}

export { Action };

the feature file will look like

Feature: Calculator and User Information

Scenario: Add two numbers
  Given I have entered 50 and 60 into the calculator
  When I press add
  Then the result should be 110 on the screen

Scenario: Get user information
  Given I want to get user information with name "User 1"
  When I get the user information
  Then the user name should be "User 1" and age should contain "5 years"

out test file (the defination file)

import { Given, When, Then } from "@cucumber/cucumber";
import { expect } from "chai";
import { Action, IUser } from "../../src";

const action: Action = new Action();
let calculator: number[] = [];
let result: number;
let userName: String;
let user: IUser | undefined;

Given(
  "I have entered {int} and {int} into the calculator",
  (num1: number, num2: number) => {
    calculator.push(num1, num2);
  }
);
Given("I want to get user information with name {string}", (name: String) => {
  userName = name;
});

When("I press add", () => {
  result = action.add(calculator);
});

When("I get the user information", () => {
  user = action.getUseName(userName);
});

Then("the result should be {int} on the screen", (expectedResult: number) => {
  expect(result).to.equal(expectedResult);
});

Then(
  "the user name should be {string} and age should contain {string}",
  (expectedName: string, expectedAge: string) => {
    expect(user?.name).to.equal(expectedName);
    expect(user?.age).to.equal(expectedAge);
  }
);

Then("no user information should be returned", () => {
  expect(user).to.be.undefined;
});

output:

cucumber-js --require-module ts-node/register --require cucumber-ts-transforms.js 'test/features/**/*.feature' --require 'test/step_definitions/**/*.ts'

......

2 scenarios (2 passed)
6 steps (6 passed)
0m00.018s (executing steps: 0m00.001s)

Conclusion:

The use of Cucumber.js provides a seamless approach to behavior-driven development (BDD) and automated testing for TypeScript and JavaScript projects. By leveraging the Gherkin syntax, scenarios are written in a human-readable format that fosters collaboration among technical and non-technical team members. The integration of step definitions translates these scenarios into executable code, ensuring accurate and repeatable tests. Cucumber's emphasis on clear requirements and real-world scenarios enhances software quality, reduces misunderstandings, and accelerates development cycles. With Cucumber.js, teams can bridge the gap between stakeholders and developers, resulting in efficient, well-tested software that aligns with user expectations.