A Detailed Introduction and How-To for Javascript BDD

BDD is a software development methodology that fosters collaboration between technical and nontechnical participants of the software development process. Since…

Testim
By Testim,
BDD is a software development methodology that fosters collaboration between technical and nontechnical participants of the software development process. Since its introduction in 2006, many teams and organizations around the world have adopted the methodology. Developers have created BDD tools for most mainstream languages. JavaScript is certainly no exception. Being the most important language in web development, JavaScript BDD is certainly a topic worth covering, and that’s what we’re doing in this post.

Defining BDD

BDD stands for behavior-driven development. It’s a software development methodology whose goal is to foster collaboration between developers and nontechnical people in a software project. In a nutshell, it consists of having the interested parties collaborate to define the requirements for the applications using a shared language, in the format of user stories and scenarios. Developers can then use those specifications to create automated tests and finally write the production code to fulfill the tests.

The end goal of BDD is to have an acceptance-testing suite that also works as documentation and a shared language for describing software requirements.

BDD Examples

Before we dive into the JavaScript specific part of the post, we’ll share some language-agnostic examples of BDD.

In BDD, it all starts with describing acceptance criteria for software requirements in the form of scenarios. The scenarios typically follow this template:

Given some initial context,
When an event occurs,
then ensure some outcomes.

Let’s say we’re designing the shopping cart functionality for an e-commerce web site. Probably the most obvious scenario one could think of is adding an item to an empty cart:

Given I have an empty shopping cart,
When I add an item to my cart,
Ensure the cart has one item.

The next step would be to make the scenarios executable by transforming them into templates developers can fill by writing the actual test code. That’s a job for BDD tools.

JavaScript BDD: How to Get Started

Time to roll up our sleeves and learn how to start using BDD on JavaScript. For this tutorial, let’s suppose we need to build a Roman numerals calculator. Here are some examples:

1st operand 2nd operand Operation Result
I I + II
V IV I
III IV * XII
LIV III / XVIII

Let’s start by creating a new Node.js project:

mkdir bdd-demo
cd bdd-demo
node init -f

The -f parameter makes Node.js create the project using all of the default options. If everything went right, you should now have a package.json file. The next step is adding Cucumber.js as a dependency to our project. Run this:

npm install cucumber --save-dev

The –save-dev option installs Cucumber.js as a development dependency, rather than a runtime one. Now we need to edit your package.json file, setting Cucumber.js as the test tool.

So, locate the following line:

"test": "echo \"Error: no test specified\" && exit 1"

And then replace it with this:

"test": "cucumber-js"

Let’s now install Chai:

npm install chai --save-dev

The complete package.json file should now look like this:

{
  "name": "bdd-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "cucumber-js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chai": "^4.2.0",
    "cucumber": "^6.0.5"
  }
}

We can already run Cucumber.js. Just type npm test, and you should get an output that looks like this:

> [email protected] test C:\Users\carlo\demos\bdd-demo
> cucumber-js

0 scenarios
0 steps
0m00.000s

We have Cucumber ready to go, so let’s write our first scenario. First, create a folder called features at the root of your project. Inside this folder, create a text file with the following content:

Feature: Addition
  In order to perform roman numeral arithmetics
  As a developer
  I want to add two roman numbers together

  Scenario: basic addition
    Given I have the number "I"
    When I add it to the number "I"
    Then the result should be the number "II"

Let’s break that down.

The first line of the excerpt of the code above defines a feature. In our case, the feature is called addition. It’s a convention to use the same name as the file. After that, we have a description of the feature, for documenting purposes, using the classic user story template:

As a < type of user >, I want < some goal > so that < some reason >.

It’s not mandatory to follow this format, though, since Cucumber.js doesn’t execute the description.

After that, we have the name of the scenario. Finally, we have the three last lines that start with Given, When, and Then, respectively. They represent our scenario’s steps.

Save the file with the name addition.feature. Then, run npm test again. The result you’ll obtain this time should look very different from the first one:

> [email protected] test C:\Users\carlo\demos\bdd-demo
> cucumber-js

UUU

Warnings:

1) Scenario: basic addition # features\addition.feature:6
   ? Given I have the number "I"
       Undefined. Implement with the following snippet:

         Given('I have the number {string}', function (string) {
           // Write code here that turns the phrase above into concrete actions
           return 'pending';
         });

   ? When I add it to the number "I"
       Undefined. Implement with the following snippet:

         When('I add it to the number {string}', function (string) {
           // Write code here that turns the phrase above into concrete actions
           return 'pending';
         });

   ? Then the result should be the number "II"
       Undefined. Implement with the following snippet:

         Then('the result should be the number {string}', function (string) {
           // Write code here that turns the phrase above into concrete actions
           return 'pending';
         });


1 scenario (1 undefined)
3 steps (3 undefined)
0m00.000s

Cucumber.js lets us know that our scenario fails because none of its steps are defined. It goes a step further and provides the snippets we can use to start implementing the steps. So, let’s use them.

Create another folder inside features called support. Inside support, create a new file with the name steps.js. Paste the following content in it:

const { Given, When, Then } = require("cucumber"); 

Given('I have the number {string}', function (string) {
   // Write code here that turns the phrase above into concrete actions
   return 'pending';
 });

 When('I add it to the number {string}', function (string) {
   // Write code here that turns the phrase above into concrete actions
   return 'pending';
 });

 Then('the result should be the number {string}', function (string) {
   // Write code here that turns the phrase above into concrete actions
   return 'pending';
 });

By this point, if we run the scenario again, Cucumber.js will tell us that the tests failed because the first step is pending, and the two subsequent ones were skipped:

The next step is writing the assertions, so our scenarios become executable tests. Start by creating a file called world.js inside of the support folder with the following content:

const { setWorldConstructor } = require("cucumber");

class CustomWorld {
  constructor() {
    this.firstOperand = '';
    this.secondOperand = '';
    this.result = '';
  }

  setFirstOperand(number) {
    this.firstOperand = number;
  }
 
  addTo(operand) {
    this.secondOperand = operand;
  }
}

setWorldConstructor(CustomWorld);

This file creates a World object, which allows you to handle isolated context for each scenario. Now head back to the file containing the step definitions. We’ll need to require Chai so we can write our assertions. Also, we’ll change the steps, so they assert against the world object. The complete file should look like this:

 const { Given, When, Then } = require("cucumber");
 const { expect } = require("chai");
 
 Given('I have the number {string}', function (string) {
   this.setFirstOperand(string);
 });

 When('I add it to the number {string}', function (string) {
   this.addTo(string);
 });

 Then('the result should be the number {string}', function (string) {
   expect(this.result).to.eql(string);
 });

Now we’re finally testing something. In the Given step, we set the first operand. “this” here allows us to access the World object.

In the When step, we add the second number to the first one. Finally, in the Then step, we use Chai’s expect style to assert that the obtained result matches the expected one.

If we run the tests now, we should see a message saying that the test failed because the result we got didn’t match what we were expecting:

Great news! Now we have a failing test that is failing for the right reasons (e.g., a failing assertion.) In the same way we do in TDD, the next step should make the test pass. The easiest way to make the scenario pass would be simply cheating: let’s hardcode the expected returning value in the addTo method located in the World file:

addTo(operand) {
    this.secondOperand = operand;
    this.result = 'II';
}

If you run the tests now, you should see a message saying that the scenario passed:

What now? The main idea is that, in a real-world project, you’d replace the code in the World object with the actual implementation.

So, the next steps now would be to add more scenarios and features until the functionality to be implemented is sufficiently documented. As an exercise, you can try and add more scenarios based on the examples table we’ve shown you at the start of the post.

Back to You

If you’re a regular reader of this blog, you’ll know that we’ve been covering many JavaScript and front-end testing related concepts. We’ve already covered topics like white-box and black-box testing and provided tutorials on end-to-end, UI, and unit testing. We shared our view on who performs testing in 2020 (spoiler alert: everyone) and even advised you to start testing in production if you don’t already do that.

Today we’ve added another entry to that list by covering JavaScript BDD. Sure, we’ve already covered Cucumber.js in the past, as well as a comparison between BDD and TDD. However, today’s post was different, since it offered a more hands-on and complete guide to the concept of BDD in JavaScript.

Stay tuned to the Testim blog in order to read more interesting posts about JavaScript and front-end testing related concepts.

What to read next

Cucumber.js for BDD: An Introductory Tutorial With Examples