NEW! Root Cause - Open Source Project for Puppeteer and Playwright MORE

Cucumber.js for BDD: An Introductory Tutorial With Examples

Cucumber.js and BDD aren't new kids on the block. But they're still fairly unfamiliar for many developers. That's unfortunate, because…

By Testim,

Cucumber.js and BDD aren’t new kids on the block. But they’re still fairly unfamiliar for many developers. That’s unfortunate, because the two can be very powerful tools for both the business people and developers. Let’s have a short look at what BDD is, and then see how Cucumber.js can be used to your advantage.

What Is BDD?

If you’re unfamiliar with BDD, don’t worry. It’s not a difficult topic. BDD is short for behavior driven development. As far as I know, the term was coined by Dan North in 2003 as a reaction to TDD (test-driven development). The lines between TDD and BDD aren’t always clear, and you might even be doing BDD without knowing it.

Basically, BDD means writing automated tests in a language that is meaningful to the business people. So where technical language is OK in TDD, you would want to avoid it in BDD. As an example, you could name a TDD-style test “getPremiumCustomerDiscountTest”, whereas in BDD you would name it something like “givenAPremiumCustomer_WhenWeGetTheDiscount_ItShouldReturnTenPercent.”

Wow! That’s quite a long name. But it does read more like a piece of documentation. A new developer can immediately understand the business rule behind it. You could even write it down as a real sentence and a non-technical user would understand it.

So is the only difference that we just use a readable sentence? Well, yes and no. The difference lies in the fact that we use the language of our end-users, the business or domain language.

BDD will often use a recurring structure in its tests, the given-when-then style of tests.

The “given” part is where you declare preconditions. In our example above we had a premium customer. Next, the “when” part contains the action you want to test. And finally, you verify the outcome in the “should” part.

When written in pure code, this can become quite a verbose sentence. For example, what if it’s the premium customer’s birthday and the discount should be higher? You’d have to write something like “givenAPremiumCustomerAndItsTheirBirthday_WhenWeGetTheDiscount_ItShouldReturnTenPercent.” As you can see, this becomes unwieldy when there are more and more preconditions and assertions.

Introducing Cucumber.js

Cucumber.js is a testing library that allows you to write your tests in plain language. It follows the given-when-then structure, but as you’ll see the tests are very readable. Even by business users. This allows your tests to be a point of communication and collaboration. They can even serve as documentation that is automatically up-to-date!

This can have far-reaching consequences. Whenever there is an issue or a feature request, both developers and business people can work on the same document. Developers can then translate this document into a test and implement the feature.

Cucumber.js (and similar tools in other languages) uses something called the Gherkin language. It’s a Domain Specific Language but it’s easily readable. Here’s what a test could look like:

See how the words Given, When and Then structure our test? A test in the Gherkin language is called a scenario. And scenario’s are organized into features. The sentence below the “Feature” part (“Premium customers should…”) is just an explanation for the reader. Cucumber.js will ignore this, because the line doesn’t begin with one of the Gherkin keywords like Feature, Scenario, Given, When or Then.

Also note how we can use tables in our tests. This can be a powerful feature when you need a lot of data in your tests. A regular unit test with QUnit or mocha won’t be very readable if it requires a lot of data.

A Sample Application

That’s all fine, but how do we actually get this scenario to run our code?

Let’s start with a sample application. I’ve (partly) followed the Angular Getting Started tutorial and it gives us an app that we can test. You can find the app here. All you need to do is follow the instructions in the readme and you’ll be able to serve it locally.

Of course, you can use Cucumber.js on any application, even applications without a UI. You don’t even have to run it against a full application. It’s entirely possible to test only specific modules, classes or files.

But we’ll be automating the browser and testing our application fully. When you run “ng serve”, you should see an application like this:

Sample-app

Writing and Running Your First Cucumber.js Test

Here’s what we’ll do. We’ll start by writing a scenario in a .feature file. Then we’ll serve our application and we’ll tell Cucumber.js to run the feature file. Cucumber.js will parse the file and call the matching pieces of code.

The Feature File

So let’s write a test first. This is a simple example that fits our sample application:

Notice how we don’t have a “Given” statement? That’s OK, we don’t need it for this test.

Simply save this in a “features” folder and give the file a name. In this case, “products-list.feature” would be a good choice.

Installing and Running Cucumber.js

To run this test, we’ll need Cucumber.js. You can install it as a development dependency by running:

Then, you can run the test by executing:

You should see some warnings, because we haven’t told Cucumber.js how each step is actually implemented:

Cucumber-warnings

Adding Step Definitions

Luckily, Cucumber.js provides some helpful messages. We need to add some code. These are called “step definitions.” In the “features” folder, create a new “steps” folder. In that folder, create a new file called “productSteps.js”. You could write it in TypeScript, but you would have to transpile the files in the “steps” folder, which is out of scope for this article.

In the step definition file, add this code:

As you can see, we now need to implement our steps. But as far as Cucumber.js is concerned, this is enough. If we tell it where our step definitions are located, it will execute these functions. What we do inside those functions, is entirely up to us.

Automating Our Browser

To automate the browser, we need to install Selenium:

You’ll also need to install a specific driver for the browser of your choice and add it to your PATH environment variable. You can find the links in the documentation. Be sure to download the version that matches the version of your browser.

Then, change the step definition code to:

Let’s break this down.

The first block is the “when” block. It defines a function that can be used in a “when” statement in a feature file:

We’re building a driver first. This will “drive” our browser. It’s the point where we can control the behavior of our browser: find elements, enter text, click links and buttons, etc.

Next, we’re telling the driver to wait until the H1 element has loaded. This is necessary because Angular will need to finish rendering before we can start testing.

As a last step, we’re loading our web page, which we served with the “ng serve” command.

Let’s look at the “then” block:

Here, we’re receiving a Cucumber.js datatable. This is the table that you can see in the feature file. We then find the products in our web page and compare it to the corresponding items in the datatable.

The last piece of code in our step definition closes the browser:

An “After” function will be run after a scenario has finished. This is called a hook and you can also have functions run before scenarios or only for specific scenarios.

Make sure you start the web application first, and then run the test again:

You will see a browser pop up, and the page load. The test will then assert the products and it will pass:

Cucumber-pass

Moving Further

This was a simple example where we didn’t even interact with the web page. But the structure is clear now:

  • write .feature files with scenarios and a given-when-then structure for each scenario
  • write step definition files where you define functions that match the steps in your scenarios
  • implement these functions as you wish (in our case we used Selenium to automate our browser)
  • run the tests by executing the cucumber-js executable in the node_modules/.bin folder

There is much more that Cucumber.js can do. For example, you can allow parameters in your step definitions, hook into the beginning or end of your scenario or test run, and tag scenarios.

The Real Power of Cucumber.js

Cucumber.js is a powerful testing framework. You can use it for unit, integration or end-to-end tests. But its real power lies in the readable language that defines the test. This allows it to become a piece of collaboration and communication between all invested parties. The tests become the documentation and vice-versa.

This post was written by Peter Morlion. Peter is a passionate programmer that helps people and companies improve the quality of their code, especially in legacy codebases. He firmly believes that industry best practices are invaluable when working towards this goal, and his specialties include TDD, DI, and SOLID principles.

Testim's latest articles, right in your inbox.

From our latest feature releases, to the way it impacts the businesses of our clients, follow the evolution of our product

Blog Subscribe