No More Seat Costs: Semaphore Plans Just Got Better!

    24 Nov 2021 · Software Engineering

    Setting Up the BDD Stack on a New Rails 4 Application

    7 min read
    Contents

    Introduction

    This tutorial guides you through generating a new Rails 4 application with RSpec and Cucumber as testing tools.

    If you’d like to learn how to set up a BDD stack on a Rails 5 application, this tutorial is the best next read for you.

    RSpec is a testing tool often used for writing unit tests, while Cucumber is a tool for writing acceptance tests. Both tools are popular choice for behavior-driven development (BDD).

    In the tutorial, we assume you are working on a Unix-like operating system.

    Prerequisites

    • Ruby.
    • SQLite (sqlite-devel package in Red Hat based distributions or libsqlite3-dev in Debian based distributions).

    Create the application

    To generate a new Rails application, we will need rails gem. Install the latest version of rails with:

    gem install rails

    Generate a new Rails application called myapp:

    rails new --skip-test-unit --skip-bundle myapp

    The --skip-test-unit option skips configuring test.unit. In the tutorial, we use RSpec instead.

    The --skip-bundle option prevent running bundle install automatically. We will do that manually:

    cd myapp
    bundle install --path vendor/bundle

    The --path parameter tells the bundle install command to install gems in the myapp/vendor/bundle directory. If you leave off the parameter, gems will be installed globally. It’s a good practice to keep gems installed locally – especially if you are working on more than one Ruby application on the development machine.

    Installing debugger gem can fail if you have Ruby 2 on your development machine:

    An error occurred while installing debugger (1.6.8), and Bundler cannot
    continue.
    Make sure that gem install debugger -v '1.6.8' succeeds before bundling.
    

    If that happens, open Gemfile in the application directory and replace gem 'debugger' with gem 'byebug'. Run bundle install --path vendor/bundle again an this time gems should install successfully. Byebug is a debugger that supports Ruby 2.

    Install RSpec

    Add rspec-rails gem to the development and test groups of your Gemfile.

    group :development, :test do
      gem 'rspec-rails'
    end

    Install the gem:

    bundle

    Bootstrap the application with RSpec:

    bundle exec rails generate rspec:install

    The task will generate RSpec configuration files.

    Install shoulda-matchers

    shoulda-matchers lets us spec common Rails functionality, like validations and associations, with less code.

    Add shoulda-matchers gem to the test group of your Gemfile:

    group :test do
      gem 'shoulda-matchers'
    end

    Install the gem:

    bundle

    Before you can use shoulda-matchers, you need to configure it by choosing the test framework and features of shoulda-matchers you want to use. Open spec/rails_helper.rb and add the following block to the end:

    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        with.test_framework :rspec
        with.library :rails
      end
    end

    You can find more info about configuring shoulda-matchers in the offical readme.

    Install factory_girl

    factory_girl is a library for setting up Ruby objects as test data. It’s essentially a fixtures replacement.

    Add factory_girl_rails gem to the development and test groups of your Gemfile:

    group :development, :test do
      gem 'rspec-rails'
      gem 'factory_girl_rails'
    end

    Install the gem:

    bundle

    factory_girl allows you to create objects that are needed in tests without providing a value for each required attribute. If you don’t provide a value for a required attribute factory_girl will use the default value that you defined in factory’s definition.

    Make Sure Everything is Connected and Working

    Let’s create an example Post model and write specs that will verify that RSpec is working correctly:

    $ bundle exec rails generate model Post title:string content:text
    invoke active_record
    create db/migrate/20140926125040_create_posts.rb
    create app/models/post.rb
    invoke rspec
    create spec/models/post_spec.rb
    invoke factory_girl
    create spec/factories/posts.rb

    Notice, the generator also creates a model spec and a Post factory. That’s the reason why we included the rspec-rails and factory_girl_rails gems in the development group of the Gemfile.

    Run the migration that will add the new posts table to the database:

    bundle exec rake db:migrate
    

    Define a post factory:

    # spec/factories/posts.rb
    FactoryGirl.define do
      factory :post do
        title "My first post"
        content "Hello, behavior-driven development world!"
      end
    end

    Update the spec to validate post’s title and content:

    # spec/models/post_spec.rb
    require 'rails_helper'
    
    RSpec.describe Post, type: :model do
      it { is_expected.to validate_presence_of(:title) }
      it { is_expected.to validate_length_of(:title).is_at_least(5) }
      it { is_expected.to validate_presence_of(:content) }
      it { is_expected.to validate_length_of(:content).is_at_least(10) }
    end

    And update the Post model with validation definitions:

    # app/models/post.rb
    class Post < ActiveRecord::Base
      validates :title, presence: true, length: { minimum: 5 }
      validates :content, presence: true, length: { minimum: 10 }
    end

    Run Post specs:

    $ bundle exec rspec spec/models/post_spec.rb
    ....
    
    Finished in 0.01982 seconds (files took 1.26 seconds to load)
    4 examples, 0 failures
    

    You can see that specs pass which means that RSpec, shoulda-matchers and factory_girl are properly configured.

    Install Cucumber

    Add cucumber-rails and database_cleaner gems to the test group of the Gemfile:

    group :test do
      gem 'shoulda-matchers'
      gem 'cucumber-rails', require: false
      gem 'database_cleaner'
    end

    The database_cleaner gem is not required, but it will make your development easier. It’s used to ensure a clean database state for testing.

    Install the gems:

    bundle

    Bootstrap the application with Cucumber:

    bundle exec rails generate cucumber:install

    The task will generate Cucumber configuration files and set up database for Cucumber tests.

    Install selenium-webdriver

    To be able to run Cucumber scenarios which use JavaScript you need selenium-webdriver. Add it to the test group of your Gemfile:

    group :test do
      gem 'cucumber-rails', require: false
      gem 'database_cleaner'
      gem 'selenium-webdriver'
    end

    And install it:

    bundle

    Make sure Cucumber is working correctly

    To do that, let’s develop a simple feature. In the scenario, a user will visit the homepage and see that a post is displayed:

    # features/home_page.feature
    Feature: Home page
    
      Scenario: Viewing application's home page
        Given there's a post titled "My first" with "Hello, BDD world!" content
        When I am on the homepage
        Then I should see the "My first" post

    Run the scenario and you will see snippets for implementing steps:

    $ bundle exec cucumber features/home_page.feature
    ...
    
    You can implement step definitions for undefined steps with these snippets:
    
    Given(/^there's a post titled "(.*?)" with "(.*?)" content$/) do |arg1, arg2|
      pending # express the regexp above with the code you wish you had
    end
    
    When(/^I am on the homepage$/) do
      pending # express the regexp above with the code you wish you had
    end
    
    Then(/^I should see the "(.*?)" post$/) do |arg1|
      pending # express the regexp above with the code you wish you had
    end
    

    Let’s copy those steps into features/step_definitions/home_page_steps.rb and edit them:

    # features/step_definitions/home_page_steps.rb
    Given(/^there's a post titled "(.*?)" with "(.*?)" content$/) do |title, content|
      @post = FactoryGirl.create(:post, title: title, content: content)
    end
    
    When(/^I am on the homepage$/) do
      visit "/"
    end
    
    Then(/^I should see the "(.*?)" post$/) do |title|
      @post = Post.find_by_title(title)
      expect(page).to have_content(@post.title)
      expect(page).to have_content(@post.content)
    end

    In these steps we create a post using factory_girl, visit the homepage and check if the post is displayed.

    If you run the scenario again, you will see that it fails since the route is not defined. Let’s add the new route:

    # config/routes.rb
    Myapp::Application.routes.draw do
      root to: "posts#index"
    end

    Now implement the controller that will serve the /posts route:

    # app/controllers/posts_controller.rb
    class PostsController < ApplicationController
      def index
        @posts = Post.all
      end
    end

    And create the view that will render Posts#index action and list all posts:

    <!-- app/views/posts/index.html.erb -->
    <ul>
      <% @posts.each do |post| %>
        <li>
          <%= post.title %><br />
          <%= post.content %>
        </li>
      <% end %>
    </ul>

    Now run the feature file and you should see it pass:

    $ bundle exec cucumber features/home_page.feature
    ...
    
    1 scenario (1 passed)
    3 steps (3 passed)

    Congratulations for making it this far. You should now be fully equipped to work in the BDD cycle and deliver clean, working code.

    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 *