Build Robust & Production Quality Applications - Lesson 3: Part 3

Items Covered:

  1. Feature Specs
  2. Capybara
  3. Request Specs

Types of Testing We've Covered:

Feature Specs

Feature Specs cover all the above in integration, although there are also specs for every item listed above, however mailers, routes, views, and helpers do not need to be tested in isolation.

With feature specs, you mimic the user's experience originating with the browser. This is the first example of vertical integration. Specs are typically broken into features.

There is something called "request specs" which focuses more horizontal integration. This is when you want to test multiple requests and responses across various controllers, making sure that things flow in a sequence.

In this course, we will NOT use requestspecs, becasue typically requestspecs are used to capture business processes from end to end. However any business process worth measuring must be centered around the customer expereince...

Capybara

The Capybara gem is the way you can simulate user interaction with the browser. Load RSpec 2.x support by adding the following line (typically to your spec_helper.rb file):

require 'capybara/rspec'

If you are using Rails, put your Capybara specs in spec/features.

If you are not using Rails, tag all the example groups in which you want to use Capybara with :type => :feature.

You can now write your specs like so:

describe "the signin process", :type => :feature do
  before :each do
    User.make(:email => 'user@example.com', :password => 'password')
  end

  it "signs me in" do
    visit '/sessions/new'
    within("#session") do
      fill_in 'Email', :with => 'user@example.com'
      fill_in 'Password', :with => 'password'
    end
    click_button 'Sign in'
    expect(page).to have_content 'Success'
  end
end

Use :js => true to switch to the Capybara.javascript_driver (:selenium by default), or provide a :driver option to switch to one specific driver. For example:

describe 'some stuff which requires js', :js => true do
  it 'will use the default js driver'
  it 'will switch to one specific driver', :driver => :webkit
end

**Capybara Built in DSL (reads more like UAT - User Accpetance tests) - reads more higher level - feature is in fact just an alias for describe ..., - :type => :feature, - background is an alias for before, scenario for it, - and given/given! aliases for let/let!, respectively.

Finally, Capybara also comes with a built in DSL for creating descriptive acceptance tests:

feature "Signing in" do
  background do
    User.make(:email => 'user@example.com', :password => 'caplin')
  end

  scenario "Signing in with correct credentials" do
    visit '/sessions/new'
    within("#session") do
      fill_in 'Email', :with => 'user@example.com'
      fill_in 'Password', :with => 'caplin'
    end
    click_button 'Sign in'
    expect(page).to have_content 'Success'
  end

  given(:other_user) { User.make(:email => 'other@example.com', :password => 'rous') }

  scenario "Signing in as another user" do
    visit '/sessions/new'
    within("#session") do
      fill_in 'Email', :with => other_user.email
      fill_in 'Password', :with => other_user.password
    end
    click_button 'Sign in'
    expect(page).to have_content 'Invalid email or password'
  end
end

Capybara Drivers

  1. RackTest is really fast, and does not really fire-up the browser. It's a headless driver therefore doesn't really simulate the browser experience. RackTest does not support Js.

  2. Selenuim is a popular driver that does handle Js and fire-ups Firefox. But this option is very slow.

  3. Capybara Webkit is another headless driver that can execute Js well. It's much faster than selenium but slower than RackTest.

First feature spec

require 'spec/helper'
feature 'User signs in' do
  background do
    User.create(username: "john", full_name: "John Doe")
  end

  scenario "with existing username" do
    visit root_path
    fill_in "Username", with: "john"
    click_button "Sign in"
    page.should have_content "John Doe"
    end
  end

The 'fill_ in "Username", ' is referring to the labeltag :username, "Username" - you can also refer to fill in the label tag, the name, or the input_ id, although its best practice to use the label tag because it easier to read.

Request Specs

Watch RailsCast video on Capybara & Request Specs

In terminal, run:

1. rails g rspec:install

2. rails g intergration_test task

3. Open up task_spec, code should be fine as is, but you would
change the request verb and the assertion

4. rake spec:requests

Example:

describe "Tasks" do
  describe "GET /tasks" do
    it  "displays tasks" do
      Task.create!(:name => "paint fence")
      get tasks_path
      response.body.should include("paint fence")
      end
    end
  end

describe "POST /tasks" do
    it "create task" do
      post_via_redirect tasks_path, tasks: {name: "mow lawn"}
      response.body.should include("mow lawn")
    end
  end
end

One issue with request specs this that they dont mimic the user's experience becasue you'e submitting the request directly, rather than going through the form like the user would - that is why we use Capybara (rather than webrat) & Launchy

Installing Capybara, allows us to change our existing tests:

describe "Tasks" do
  describe "GET /tasks" do
    it  "displays tasks" do
      Task.create!(:name => "paint fence")
      visit tasks_path
      page.should include("paint fence")
      end
    end
  end

describe "POST /tasks" do
    it "create task" do
      visit tasks_path
      fill_in "New Task", with: "mow lawn"
      click_button "Add"
      page.should have_content("successfully added task")
      page.should have_content("mow lawn")
    end
  end
end

*What if the test fails and you don't know why? Thats where the gem "Launchy" comes in: -anywhere in the test, you can write:

save_and_open_page

Capybara does not test Javascript with out Selenium:

it "supports js", js: true do
   visit tasks_path
   click_link "test js"
   page.should have_content("js works")
  end
end

Let's try adding Js to our first test and see if it is supported by Selenium.

describe "Tasks" do
describe "GET /tasks" do, js: true do
  it  "displays tasks" do
    Task.create!(:name => "paint fence")
    visit tasks_path
    page.should include("paint fence")
    end
  end
end

The test will fail because Selenium does not support database transactions

In #spec_helper.rb:

Change config.use_transactional_ fixtures = true, to false.

Selenium does not support database transactions.

But this will mean our database transactions will carryover in between tests and we dont want that. So the answer is the database cleaner gem that will run in between tests. You must add the config code from the documentation into the spec_helper, removing transactions since the arent supported by Selenuim.


To run pry in a spec, you must use: require 'pry'; binding.pry before the error/after the action During pry. you can use

Launchy "save_and_open_page"