24 Nov 2021 · Software Engineering

    Introduction to Writing Acceptance Tests with Cucumber

    9 min read


    In this article, we’re going to look at Cucumber as a tool for writing your customer acceptance tests. More specifically, we’re going to look at how a Cucumber acceptance test might look in practice. After reading this article, you should have a clearer picture of why Cucumber is a good candidate for writing your acceptance tests.

    It’s also worth mentioning that, in a BDD fashion, you should start writing your acceptance test first, and it should drive your next steps, pushing you into deeper layers of testing and writing implementation code. So, as an example, your workflow should look similar to this:

    1. Write your acceptance test
    2. See it fail so you know what the next step is
    3. Write a unit test for that next step
    4. See it fail so you know what the implementation needs to be
    5. Repeat steps 3 and 4 until you have everything you need, and all your tests (including the acceptance one) are passing
    6. Rinse and repeat for every new feature.

    If you would like to see the final code listed in the examples, there’s a GitHub repository available.

    Hello Cucumber Example

    To get things started, we’re going to look at a rather simple example, so you can familiarise yourself with the syntax and basic file structure:

    # feature/hello_cucumber.feature
    Feature: Hello Cucumber
      As a product manager
      I want our users to be greeted when they visit our site
      So that they have a better experience
      Scenario: User sees the welcome message
        When I go to the homepage
        Then I should see the welcome message

    The first part starting with the keyword Feature is called a feature description. It needs to have a feature title, which is the string “Hello Cucumber” in our case. It can also have an optional description (the text underneath the title), which is meant to help the reader understand the feature and its context.

    When writing your Cucumber features, it’s good practice to follow the user story style, which looks like the following:

      As a 
      I want to 
      So that 

    Steps and Step Definitions

    The Cucumber feature we’ve written is readable, but how do we get it to do something? Does that plain text have anything to do with our code? Well, those scenario instructions are called steps, and we’re going to use them to drive our tests.

    The way it works is, for each step in our scenario, we’re going to provide a block of Ruby code to be executed. We’re going to place our step definitions (the blocks of code) in a file called hello_steps.rb.

    When(/^I go to the homepage$/) do
      visit root_path
    Then(/^I should see the welcome message$/) do
      expect(page).to have_content("Hello Cucumber")

    As you can see, we’re simply associating each line in our Cucumber feature file, called a scenario step, with its corresponding step definition, matching the step definition string with the use of a regular expression.

    So, in the first step definition, we’re saying that, in order to go to the homepage, the user will visit the root_path (which is standard Rails terminology, and it’s something we define in your config/routes.rb file). For the expectation step, we’re going to check that the homepage contains the “Hello Cucumber” text.

    # config/routes.rb
    Rails.application.routes.draw do
      root 'welcome#index'
    # app/controllers/welcome_controller.rb
    class WelcomeController < ApplicationController
      def index
    # app/views/welcome/index.html.erb

    Hello Cucumber

    $ cucumber -s
    Using the default profile…
    Feature: Hello Cucumber
      Scenario: User sees the welcome message
          When I go to the homepage
          Then I should see the welcome message
    1 scenario (1 passed)
    2 steps (2 passed)

    The -s flag tells Cucumber to hide the location of each step definition, which is the default behavior.

    Can I Test My JavaScript?

    Cucumber lets you test your application from the user's perspective, and in some cases that means having to deal with JavaScript-driven user interface elements. Cucumber does this by starting a browser in the background, and doing what a real user would do by clicking on links, filling out forms, etc. You should not use Cucumber to unit test your JavaScript-drivencript code, but it's perfect for testing user interaction.

    The default driver Cucumber uses through Capybara is called :rack_test. It has some limitations, mainly the fact that it does not support JavaScript, so we'll need to add another driver that supports JavaScript, and use it for those features that require it.

    We will use the :rack_test driver for all of our tests that don't depend on JavaScript because it's faster, as it doesn't have to open a web browser program. For tests that require JavaScript, we will use the Selenium driver. Selenium is based on launching and controlling an instance of your local Firefox browser, so you need to make sure you have Firefox installed.

    Add the following line to your Gemfile's :test group:

    gem 'selenium-webdriver'

    Let's See a Cucumber and JavaScript Example

    For this example, we're going to have a link that, when clicked, replaces the contents of the page with the string "Link Clicked" via Javascript. In order to differentiate between our regular (rack_test driven tests) and the ones that require JavaScript, we will use a Cucumber tag. It looks like this: @javascript.

    # features/link_click.feature
    Feature: Link Click
      Scenario: User clicks the link
        Given I am on the homepage
        When I click the provided link
        Then I should see the link click confirmation

    Now that we have our feature, we need to add some step definitions. Note that in this case we have a new Given step, which sets the context in which the action (When) is triggered.

    # features/step_definitions/link_click_steps.rb
    Given(/^I am on the homepage$/) do
      visit root_path
    When(/^I click the provided link$/) do
      click_on "js-click-me"
    Then(/^I should see the link click confirmation$/) do
      expect(page).to have_content("Link Clicked")

    For the Given step, we're going to do the same thing we did in our first example — we're going to visit the homepage. Next, we're going to click the link on the homepage, and finally we're going to check that the page contains the string "Link Clicked".

    Let's also add the link to our homepage, so that we have something to click on:

    Hello Cucumber

    <%= link_to "Click Me", "", :id => "js-click-me" %>

    The last missing piece of the puzzle is the JavaScript code that is going to listen for the click event on the link, and when it receives it, it will go ahead and replace the page contents with the "Link Clicked" string:

    $(document).click("#js-click-me", function(event) {
      $("body").html("Link Clicked");

    Without the @javascript tag in the Cucumber feature file, this JavaScript code would never get executed, since it requires a JavaScript-aware browser.

    Why Not Use RSpec and Capybara Instead?

    Just in case you were wondering, since Capybara seems to be a popular choice among Ruby on Rails developers, we're going to take a short detour and list a few things that make Cucumber a better choice for writing acceptance tests compared to using RSpec and Capybara directly together.

    Choosing Cucumber over Capybara has a few benefits, some of which are less apparent at first sight. Note, though, that Cucumber uses Capybara behind the scenes, it's just that it provides a nice language abstraction layer on top of it.

    • The most obvious reason for choosing Cucumber is that it seems to appeal to non-technical people, and it's said that, in an ideal world, the customer would be able to write the acceptance tests himself.
    • The second, less obvious, but most important reason is the fact that it forces you (the developer) into business mode. It helps you switch gears for a second and look at your code architecture from a different point of view, one which helps you plan and implement each feature systematically.
    • Documentation is also another great benefit you can get as a side effect of writing your features in a language that is easier to read.

    Continuous Integration for Cucumber on Semaphore

    By setting up continuous integration the tests you have written can run automatically on every git push you do.

    Semaphore is a hosted CI service which comes with all recent versions of Ruby preinstalled, so it takes minimum effort to get started.

    First, sign up for a free Semaphore account if you don’t have one already. All there's left to do is to add your repository.

    Semaphore will analyze your project and recommend commands for everything to run smoothly. Also, the cucumber job command we need will be added:

    bundle exec rake cucumber

    From now on, Semaphore will run your tests for you on every git push.


    Now that we've looked at a few examples of using Cucumber in practice, you should be confident enough to start writing your acceptance tests with it. Don't expect to have all the right words for your first few tests, finding the right vocabulary takes some practice.

    Whether you are looking to take control of a legacy project that’s falling apart without tests, or would like to adopt BDD in a fresh project, Cucumber is a tool that can give you both immediate and long-term benefits. Not only that, but adding Cucumber to your Rails stack is very easy when you know how to do it.

    Now, go ahead and write some awesome tests.

    P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We've recently published an ebook covering just that — "Rails Testing Handbook". Learn more and download a free copy.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Writen by:
    Cezar is a web technology specialist and the owner of Mix & Go, where he helps early-stage startups bring new SaaS products to market.