Testing rails app with RSpec, FactoryGirl, Capybara, Selenium & Headless

In this blog I don’t have any intention to argue or discuss the necessity of Test Driven Development (TDD), but indeed without any doubt I believe testing is necessary. The other day I was watching a talk by Kent Beck where he said “if you don’t find Testing necessary for your code, then don’t do that. Whats the point in arguing about it?” I am also believer of that, as he said testing is all about gaining confidence. Now in this blog I am putting it together how did I use Rspec , Capybara , Selenium to gain this “confidence”.

RSpec has a different repository for rails (https://github.com/rspec/rspec-rails) and a different gem. To put RSpec in project we need to put gem rspec-rails in Gemfilelike this:

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

then do a:

bundler install

Now we need to run a generator command:

rails generate rspec:install

Which basically generates following files:

spec/spec_helper.rb
spec/rails_helper.rb

Later in this blog we will configure it. Now we will write some RSpec tests. RSpec tests can be for many things and of many type. It can be for models, it can be for controllers, it can be for features and so on. There are generator commands to help you creating files.

When I run:

rails generate rspec:features home_page

It creates spec/features/home_page_spec.rb. As you see, every rspec files end with . So when we run bundle exec rspec _spec.rb it finds all these files and test them, we can also run:
bundle exec rspec spec/features

Which will only run feature test. We can also do:

bundle exec rspec spec/features/home_page_spec.rb:2

which will run test of that line until it ends. Now time to write some tests. Before writing test one thing we always need to keep in mind, in every test case we will test only one feature and we will focus only on that feature. (Maybe in later blog I will describe mock and stubs two cool features). If we don’t focus on one feature then it will take more time to debug our test case then writing the patch.

In our RSpec block we will be using describe and it methods. its are the functionality tests and a describe can contain a lot of its. A describe block can hold bunch of it blocks. In another word given/let calls are used at the top of a feature/describe/contextblock and apply to all contained feature/describe/context or scenario/it blocks.

in

spec/features/home_page_spec.rb
require 'rails_helper'
RSpec.feature "home page", type: :feature do
describe "navigation" do
it " shows me menu" do
get "/"
#do testing
assert_select ".navbar", :text => "home"
end
end
it "shows title" # it shows pending because it does not have do block
end

There are alias methods, and other way to write, for an instance feature is an alias for describe …, :type => :feature , background is an alias for before , scenario for it , and given/given! aliases for let/let! , respectively and so on.

We can see that RSpec by default deals with requests like get , post , it is not a browser a user will use see the product. But the main point is it does not have js support and modern website is full of javascripts. So what can I do? We will be using Capybara to mimic browser. We will add following gem to our Gemfile before doing

bundler install

:

gem 'capybara'

Now it is time to re write our test code. We need to add require ‘capybara/rails’ in our spec file.

require 'capybara/rails'
require 'rails_helper'
RSpec.feature "home page", type: :feature do
describe "navigation" do
it " shows me menu" do
visit "/"
expect(page.find('.nav-bar')).to have_content("brand") # page find search for css path
end
end
end

A Capybara cheat sheet which I found to be very useful can be found and here : https://gist.github.com/zhengjia/428105

Now we will integrate:

gem "FactoryGirl"

FactoryGirl is awesome tool to automate model entry creation. Suppose we have a BlogPost model which has many Catogories and one Blogger . In real life this is just a simple model will look like this when we write test case:

blog_post=BlogPost.new({title:"this is title",body:"this is body"})
blog_post.category<<Category.create(name:"technology")
blog_post.blogger=User.create(username="sadaf",password="password",rewritepassword="password")
blog_post.save()

For each post we will need to write this many line of code, and every post will need to have hand written title and body which is tiresome. FactoryGirl solves this problem. We will define Factories in support/factories.rb :

FactoryGirl.define do #active admin factory :admin_user, :class => AdminUser do
sequence(:email){|n| "email#{n}@example.com"}
password "password"
password_confirmation "password"
end
factory :category, :class => Category do
name "category name"
end
factory :blog_post, :class => BlogPost do
association :blogger, factory: :admin_user
sequence(:title){|n| "this is super cool title #{n}"}
sequence(:title){|n| "this is" + " super duper " * n + " body."}
description "static description"

factory :blog_post_with_category do
after(:create) do |post|
create(:category, blog_post: post)
end
end
end
end

so to create AdminUser , using factory_girl we will do:

FactoryGirl.create(:admin_user)
1
FactoryGirl.create(:admin_user)
Every time we call above code it creates different email address, email1@example.com, email2@example.com .., but password remains the same for all cases as it is not in sequence block. BlogPost has a association. and when we will do FactoryGirl.create(:blog_post) it will also create admin_user to satisfy its need, so we don’t need to write it separately. And to create blog_post with category we will need to do following:

blog_post = create(:blog_post_with_category)

Life is pretty easy right now.

....
it " shows me sub-menu on parent-menu click" do
visit "/"
blog_post = create(:blog_post_with_category
expect(page).to have_content(blog_post.title)
end
...

Now we may like to test our beautiful nested navbar is working or not, which is beautiful because it is manipulated by javascript. To do that we need to tag our it or describe block with “js”, which is very simple just add js:true as it or describes parameter and it is done. But before we write our test we will need to add another gem for our webdriver.

gem 'selenium-webdriver'

Now we can write:

describe "navigation" do
....
it " shows me sub-menu on parent-menu click", js:true do
visit "/"
click("parent menu") #text of button
expect(page).not_to have_selector('.submenu',visible:true) # if visible or not
expect(page.find('.submenu')).to have_content("submenu")
end
...
end

So far we run bundle exec rspec we did not see any browser, but now we will see a browser popping up and popping down. Which can be annoying sometime. To solve this either we can use webkit instead of seleniumbut in this blog I will use another gem called headless which turns selenium into a ui less browser which is super cool.

gem 'headless'

then we need to install Xvfb which is as easy as typing

sudo apt-get install Xvfb

Then we need to modify my spec_helper.rb . We need to add following lines at the beginning of the file:

if ENV['HEADLESS'] == 'true'
# require 'rubygems'
require 'headless' # if face load error read below
headless = Headless.new
headless.start
at_exit do
exit_status = $!.status if $!.is_a?(SystemExit)
headless.destroy
exit exit_status if exit_status
end
end

This code is responsible to instantiate headless when we type: ENV=HEADLESS bundle exec rspec and to destroy it. Then in between configure block we need to put before and after blocks which changes browser to selenium for js:

RSpec.configure do |config|
....
config.before(:each) do |example|
if example.metadata[:type] == :request and example.metadata[:js]
Capybara.current_driver = :selenium
end
end
config.after(:each) do |example|
Capybara.use_default_driver
end
....
end

If you see problem loading headless gem.

LoadError: cannot load such file -- headless
from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'

Then you can make headless gem directory executable to all user using chmod:

gem which headless

for me:

var/lib/gems/2.1.0/gems/headless-2.2.0/lib/headless.rb

And then copy the directory of gem before lib and chmod it to 655:

chmod -R 655 /var/lib/gems/2.1.0/gems/headless-2.2.0