Introduction to Writing Acceptance Tests with Cucumber

Improve your testing skills with acceptance testing. Cucumber makes you a better developer by helping you see your code through the eyes of the user.

Brought to you by

Semaphore

Introduction

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:

Feature: <feature title>
  As a <persona|role>
  I want to <action>
  So that <outcome>

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
end

Then(/^I should see the welcome message$/) do
  expect(page).to have_content("Hello Cucumber")
end

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'
end

# app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
  def index
  end
end
# app/views/welcome/index.html.erb
<h1>Hello Cucumber</h1>
$ 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)
0m0.168s

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

  @javascript
  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
end

When(/^I click the provided link$/) do
  click_on "js-click-me"
end

Then(/^I should see the link click confirmation$/) do
  expect(page).to have_content("Link Clicked")
end

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:

<h1>Hello Cucumber</h1>

<%= 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) {
  event.preventDefault();
  $("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.

Conclusion

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. Semaphore is working on a book "The Ultimate Guide to BDD with Rails". Sign up to receive a FREE copy.

734c1a6ba4b86b87a2b5e52a71717e60
Cezar Halmagean

Cezar is the founder and senior engineer at Mix & Go, a Ruby on Rails consultancy in Romania. You can follow him on Twitter at @chalmagean.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.