Build Robust & Production Quality Applications - Lesson 3: Part 3
07 Mar 2015Items Covered:
- Feature Specs
- Capybara
- Request Specs
Types of Testing We've Covered:
- Models (Model Specs)
- Controllers (Controller Specs)
- Views, Routes, Helpers, Mailers (Feature Specs)
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
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.
Selenuim is a popular driver that does handle Js and fire-ups Firefox. But this option is very slow.
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
- Request Specs are alternatives to the Rails Builtin Integration Testing (Refer to RailsCast video 187)
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"