Rails feature testing Logbook

Notes of a developer using Rspec + Capybara

Posted on April 26, 2015

After covering your code with unit tests and validating major-feature integration, it's time to dive into Acceptance (Feature) tests. When you have the opportunity to start everything correctly, by using Behavior-Driven Development (BDD), or when a Test Engineer takes care of that, things may be sweet. But, from where to start when you aren't such lucky ?

In case of a Rails App, Rspec + Capybara are a perfect combination. However, you might have to fight some battles (with brave and honor ) at the beginning. These are brief notes of a developer who used to work daily with Java and now is doing the same with Ruby.

Set up

For a basic set up, you can start by adding these gems to your Gemfile and run bundle install.

group :test do
  gem 'rspec-rails'
  gem 'database_cleaner'
  gem 'capybara'
  gem 'poltergeist'
  gem 'selenium-webdriver'
end

Briefly explaining, rspec-rails is needed to add Rspec to Rails. The gem database_cleaner is used to clean (remove test data) of our test database after each test execution. Then, capybara is our acceptance testing framework.

An acceptance test framework needs a web driver, poltergeist is PhantomJs driver which allows you to run Capybara on headless mode. It also requires you to have PhantomJs installed. I had to install the version 1.9.8 of PhantomJs since newest versions broke image uploading tests.

Finally, if you want to see your tests running in a real browser, for debugging purposes, a good tip is to install selenium-webdriver. This driver needs Mozilla Firefox web browser installed.

Feature/Scenario Organization

Applying BDD with a framework such as Cucumber, is an awesome option to keep the specs well organized and very descriptive. Although we are not using Cucumber, we can achieve the same benefits.

Chris Zetter proposed in his post a great approach to write well readable Rspec + Capybara specs. Based on this post, we can organize a sign up feature spec into something like this:

feature "User sign up" do
  scenario "with email" do
    click_to_sign_up
    provide_basic_info
    upload_avatar
    set_languages
    set_background
    end_sign_up
  end

  def click_to_sign_up
    visit "/users/sign_up"
    click_link "sign_up_with_email_btn"
  end

  def provide_basic_info
    user_id = timestamp
    within "#new_user" do
      expect(page).to have_text("Sign up as a Traveler")

      fill_in "user_first_name", with: "Test"
      fill_in "user_last_name", with: "Volunteer #{user_id}"
      fill_in "user_email", with: "test_volunteer_#{user_id}@mail.com"
      check "user_newsletter_opt_in"
      find('input[type="submit"]').click
    end
  end

  def upload_avatar
    expect(page).to have_text("Upload Photo")
    attach_file "user_avatar", File.absolute_path("spec/fixtures/images/profile.png")
    find("#upload_avatar").click
  end

  ...

end

Basically, we divide our test scenario into test steps where each step is an isolated method. Each method can have, if makes sense, an expect assertion that validates a test step.

As can be noticed, provide_basic_info step has the within command. This a good practice to fill in a form since this command waits for the HTML element to be loaded in the page. Another good tip is to use timestamps in our test data. Here, timestamp is an method defined in a helper class.

Database cleaner

To end this post I only would to like to highlight the importance of the database_cleaner gem. When we have an integration continuous environment, we don't want to keep old test data in our test database. Besides, acceptance test data may impact the execution of integration tests.

To help in this issue, database_cleaner, as its own name says, cleans the database after each test case or test suite execution. By default, all tables are cleaned. However, you can set which tables must be untouched.

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation,  except: %w[skills countries languages])
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end  

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

The config above is defined in spec/support/database_cleaner.rb. In this example, I set database_cleaner to don't erase skills, countries and languages. The content of those tables are used in combo boxes and other elements that are present in the sign up feature flow.

That's it !! I hope these humble notes help you to get started in your Rspec + Capybara specs