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:

  • 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

  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

  • 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"

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

Items Covered:

  1. Structural Refactor
  2. Skinny Controller, Fat Models
  3. Rspec Macros
  4. Rspec Shared Examples

Structural Refactor

Controllers are more of traffic control and methods containing logic should refarctored to the model level

  • For simple refactors such as moving methods from controllers to methods, since your test suite is already comprehensive enough to cover the current model, you dont have to create additional tests at the model level, only if you decide to add additional functionality.

Typically you shouldn't have more than one conditional or return from a method within a method. You should refactor by moving a piece of the code to private method.

Skinny Controller, Fat Models

One of the most common refactors in Rails and one of the most well-known architectural principals in rails.

Fat Models, means the models assume the most responsibilty. Good blog post to read: Skinny Models, Fat Controllers Here - James talks about moving responsibilty from the view, to the controller, and then to the model.

Examples of Opensource Project Management Tools that have need to go on a Fat Controller Diet: ChilliProject Redmine

Refactoring to fat models in MyFlix

Tip: When refactoring your controllers to fat models, do not move methods that take parameters in a form because that is the purpose of the controller - to talk to the views and models. Look for those that iterate through the model.

Rspec Macros

Allows you to extract logic that can be used by multiple rspec controllers.

So rather than the following at the begining of each controller test:

before do
  frank = Fabricate(:user)
  session[:user_id] = frank.id
end

We could create a macro, spec/support/macros.rb and include the above code:

def set_current_user
  frank = Fabricate(:user)
    session[:user_id] = frank.id
  end

def current_user
  User.find(session[user_id])
end

AND in your test:

before {set_current_user}

Now, if I want to clear the current user to test the path of an unauthorized user create another macro called clearcurrentuser, make it the first line in your test.

def clear_current_user
  session[user_id] = nil
end

Shared Examples

What bout you want to test the redirect for unauthenticated users for not just index but index and new

Create a Shared Example:

#spec/support/shares_examples.rb
shared_examples "require_sign_in" do
  it "redirects to the front page" do
    response.should redirect_to root_path
  end
end

In your test: you can't just write:

it behaves_like "require_sign_in"

because you have to clear the current user and hit the action first:

context "user not signed in" do
  before do
    clear_current_user
    get :index
  end
  it behaves_like "require_sign_in"
end

But this is frustrating becasue although we DRY'd up our code a little, we still have to add a before block for the clearcurrentuser and the index.

It would be great if there's a way to pass the action to the shared example, and there is:

shared_examples "require_sign_in" do
  it "redirects to the front page" do
    clear_current_user
    action
    response.should redirect_to root_path
  end
end

AND in your test:

context "not_signed_in" do
    it_behaves_like "require_sign_in" do
    let(:action) { get :index }
  end
end

AND you actually don't need the context anymore:

<index>
it_behaves_like "require_sign_in" do
  let(:action) { get :index }
end

<new>
it_behaves_like "require_sign_in" do
  let(:action) { get :new }
end

<create>
it_behaves_like "require_sign_in" do
  let(:action) { post :create, todo: {name: 'something'} }
end

Shared examples can be used on both controller and model levels For more info:

Shared Examples Docs

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

Items Covered:

  1. Growing Complexity
  2. Interactive Debugging for Solution Delivery
  3. Transactions

Growing Complexity Guided By Tests

Tags.all.map(&:name) == Tags.all.map{|tag| tag.name}

def create
  @todo = Todo.new(params[:todo])
  if @todo.save
    location_string = @todo.name.split('at').last.strip
    @todo.tags.create(name: "location#(location_string)")
    redrect_to root_path
  else
  render :new
  end
end

strip method removes all spaces.

def create
  @todo = Todo.new(params[:todo])
  if @todo.save
    location_string = @todo.name.split('at').last.strip
    locations = location_string.split('and')
    locations.each do |location|
    @todo.tags.create(name: "location:#(location)")
    redrect_to root_path
  else
  render :new
  end
end

When to use each vs map

each is used for when you want to iterate through a an collection of data, an array for example but you dont want to manipulate the result. map is ideal for when iterating over a collection and then you need to alter of manipulate those returned values for input into another problem.

When to use split vs regex

Use split when searching for one element And regex when searching for more than one.

Interactive Debugging for Solution Discovery

After writing and implementing your test cases, you should run sanity checks within your browser. If bugs are identified, you should run write tests to isolate it the bug.

Use Binding.pry to in your controller being tested, after the problematic line. Binding.pry is really just irb so you now have a means to experiment with your code until you find the right solution.

For a good regex tutorial: Regex Tutorial Rubular

.split method cannot run on nil so Ruby 1.9 introduced .try(:split), which will run .split if not nil.

Transactions

Wrapping batch operations into transactions:

To update the queue in MyFlix, the position entered must be an integer. If not, the entire page will fail to update. This is unlike normal transactions where we can include a validation error. This is where the concept of transactions come in. If all actions in the transaction do not save, it will rollback.

Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs. So basically you should use transaction blocks whenever you have a number of statements that must be executed together or not at all.

Build Robust & Production Quality Applications - Lesson 2: Generate Fake Data with Faker

Use Faker to Generate Fake Data

Creating fake data when your model has uniqueness validations will throw errors if your data is not unique.

Fabricator(:todo) do
  name { "cooking" }
  user
end
Fabricator(:user) do
  email { "joe@example.com"} #This will throw an error eventually if email record already exists.
end

Examples

Creating a category:

Fabricator(:category) do
  name { Faker::Name.name }
end

Creating a video:

Fabricator(:video) do
  title { Faker::Lorem.words(5).join(" ") }
  description { Faker::Lorem.paragraph(2) }
end

Creating an invitation:

Fabricator(:invitation) do
  recipient_name { Faker::Name.name }
  recipient_email { Faker::Internet.email }
  message { Faker::Lorem.paragraphs(2).join(" ") }
end

Read more: Faker Docs

Build Robust & Production Quality Applications - Lesson 2: Object Generation with Fabricators

It's typical to create many objects during testing and the more attributes you have the more cumbersome it could be to maintain, moreover, if an attribute changes, it then becomes very difficult to update all those tests.

To make this better, we can use an object generator framework called Fabrication.

Steps:

1. Create a folder under specs called "Fabricators"

2. create fabricator_todo.rb

Fabricator(:todo) do
    name { "cooking" }
  end

3. You can also create a user each time a todo is created:

Fabricator(:todo) do
  name { "cooking" }
  user { Fabricate(:user) }
end

or

Fabricator(:todo) do
  name { "cooking" }
  user
end

In your test suite:

If you want to create an object in your database:

describe "#display_text" do
  let(:todo) { fabricate(:todo) }
  subject { todo.display_text }

If you would rather create an object in memory, you could:

describe "#display_text" do
  let(:todo) { fabricate.build(:todo) }
  subject { todo.display_text }