DEV Community

Cover image for BDD Testing with Cucumber
Joe Avila
Joe Avila

Posted on

BDD Testing with Cucumber

Recently at work I got the opportunity to write Behavior Driven Development tests using Cucumber.js. Cucumber uses a language called Gherkin to write the test steps and uses javascript to execute them. Gherkin is written in plain english (or a selection of other languages) and is made to be read by any (particularly non-dev) team members. If you haven't written tests before Cucumber is a great place to start.

Setting up the WebDriver

// ~/cuc-test/bdd/features/step_definitions/stepdefs.js
const { Builder, Capabilities, By } = require('selenium-webdriver')

const driver = new Builder().withCapabilities(Capabilities.chrome()).build()
Enter fullscreen mode Exit fullscreen mode

Selenium WebDriver is the technology we'll use to run our tests. At a high level, it follows the test steps and executes them in a browser. Builder creates the driver when we run our tests. Capabilities is how you declare your browser. By is a keyword we'll use in our tests to define what we're looking for.

I found the documentation for Selenium a bit hard to navigate. I mostly referenced the API documentation for Javascript or just searched the issue I was having.

Gherkin keywords

Screen shot of the initial test page

I spun up a simple html page with a few headers, a text input, a drop down menu, and a button to submit your changes. The first test I wrote checks that the default values are all as I'd expect them to be.

Scenario: I want to check default values of the page
    Given I am on the home page
    Then I should see the default title
    And I should see the text input field
Enter fullscreen mode Exit fullscreen mode

Scenario, Given, Then & And are all keywords that come from the gherkin syntax. In my Scenario statement I summarize what the following test steps do. This keyword is used to group steps together and give context for users running the tests.

Given, Then & And declare steps for a test. The string following these declaration keywords connect the step and code to execute. If you have a typo or don't match them exactly you'll receive an Undefined error when you run the tests. This is the meat and potatoes of Cucumber testing.

Sasha with Meat and Potatoes GIF

Given('I am on the home page', function () {
    driver.get('localhost:3000/')
})

Then('I should see the default title', function () {
    driver.findElement(By.xpath(`.//*[text()[contains(.,'Welcome to the site.')]]`))
})

Then('I should see the text input field', function () {
    driver.findElement(By.id('textbox'))
})
Enter fullscreen mode Exit fullscreen mode

The first step of my scenario navigates to the the page I pass into the get method. The second step tells the webdriver to search the page for the text I pass into the xpath. The third searches for any element with an ID of textbox. Remember, we imported By from Selenium in our set up. There a plenty of options to search by.

Scenario: I want to make sure all the colors work
        Given I am on the home page
        Then I want to select the color 'Green' from the drop down
        And I should submit the changes
        Then I should make sure the third header is 'Green'
Enter fullscreen mode Exit fullscreen mode

Since I'm checking the default values in the last scenario I hardcode the parameters to search for. But it's common to pass parameters through the test steps. Notice how green is in quotations in steps two and four.

Then('I want to select the color {string} from the drop down', function (string) {
    const dropDown = driver.findElement(By.id('selector'))
    dropDown.click()
    dropDown.sendKeys(string)
    dropDown.click()
})

Then('I should make sure the third header is {string}', async function (string) {
    const color = await driver.findElement(By.id('changing-header')).getCssValue('color')
    // getCSSValue returns an rgb value
    // colorMap is an object with keys of string and values of the associated value
    expect(color).to.equal(colorMap[string])
})
Enter fullscreen mode Exit fullscreen mode

Then is a function that takes two parameters. The first parameter is the string that we used to declare the test step. Inside of that string we signal a nested variable by wrapping it in curly braces, {string}.

The second parameter is a callback function, where we pass in the variable. Cucumber is particular about what you call the variable, if you pass in a number character you'd use int. If you have more than one variable you'd declare it as string, string2, string3 in the callback but just as {string} in the first parameter.

I also used a couple other methods of the driver like click(), sendKeys(string), and getCssValue. Those methods do exactly what the name implies. sendKeys sends the string you pass in to the value field.


While working on writing a bunch of tests for old components I began to think of the tests I would write as I created new components. What types of identifiers does this component need to be testable? Is it a class name, an ID, or certain unique text that won't appear elsewhere when this is loaded? I began to think about how I would write my new components to comply with the tests I'd eventually write. In some cases, I started with the tests first. Cucumber encourages that by giving giving you the line of code you need to write when it encounters a step it doesn't recognize.

Undefined error thrown by Cucumber

You can fork this repo and open it up on your machine if you'd like to learn by playing instead. There's a couple of tests that need to fixing in order to pass. Feel free to submit a PR if you come up with any cool tests.

Cover image: "cucumbers en route to pickledom" by Stacy Spensley is licensed with CC BY 2.0.

Top comments (0)