Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does RSpec's expect work in ROR

While going through Ruby On Rails Tutorial by Michael Hartl,in the section where the author writes integration test to validate his Signup page, he has used code spinet bellow. I got what the code does but couldn't get my head around the 'how' part i.e. couldn't understand order of execution.

expect { click_button "Create my account" }.not_to change(User, :count)

Can someone please explain the semantics of the above chain of methods and blocks and how they fit together?

like image 321
Bedasso Avatar asked May 18 '12 01:05

Bedasso


2 Answers

You'd use expect ... change to verify that a particular method call changes -- or does not change -- some other value. In this case:

expect { click_button "Create my account" }.not_to change(User, :count)

will cause rspec to do the following:

  1. Run User.count and note the value returned. (This can be specified as a receiver and a method name, like (User, :count) in your example, or as an arbitrary block of code, like { User.count }.
  2. Run click_button "Create my account" which is a Capybara method that simulates a mouse click on a link.
  3. Run User.count again.
  4. Compare the results of #1 and #3. If they differ, the example fails. If they're the same, it passes.

Other ways to use expect ... change:

expect { thing.destroy }.to change(Thing, :count).from(1).to(0)
expect { thing.tax = 5 }.to change { thing.total_price }.by(5)
expect { thing.save! }.to raise_error
expect { thing.symbolize_name }.to change { thing.name }.from(String).to(Symbol)

Some docs are here.

How it does this is a bit arcane, and it's not at all necessary to understand how it works in order to use it. A call to expect is defining a structure for rspec to execute, using rspec's own custom DSL and system of "matchers." Gary Bernhardt has a rather neat screencast in which he argues that the mystery of rspec actually falls out naturally from a dynamic language like ruby. It's not a good introduction to using rspec, but if you're curious about how it all works, you might find it interesting.

UPDATE

After seeing your comment on another answer, I'll add a bit about the order of operations. The unintuitive trick is that it's the matcher (change in this case) that executes all of the blocks. expect has a lambda, not_to is an alias for should_not whose job is to pass the lambda on to the matcher. The matcher in this case is change which knows to execute its own argument once, then execute the lambda that it was passed (the one from expect), then run its own argument again to see if things changed. It's tricky because the line looks like it should execute left to right, but since most of the pieces are just passing around blocks of code, they can and do shuffle them into whatever order makes the most sense to the matcher.

I'm not an expert on the rspec internals, but that's my understanding of the basic idea.

like image 110
Rob Davis Avatar answered Oct 11 '22 02:10

Rob Davis


Here's an excerpt from from Ryan Bates Railscast on Request Specs and Capybara

require 'spec_helper'  

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

  describe "POST /tasks" do  
    it "creates a task" do  
      visit tasks_path  
      fill_in "Name", :with => "mow lawn"  
      click_button "Add"  
      page.should have_content("Successfully added task.")  
      page.should have_content("mow lawn")  
    end  
  end  
end  

And here's an excerpt from the docs on RSPec Expectations

describe Counter, "#increment" do
  it "should increment the count" do
    expect{Counter.increment}.to change{Counter.count}.from(0).to(1)
  end

  # deliberate failure
  it "should increment the count by 2" do
    expect{Counter.increment}.to change{Counter.count}.by(2)
  end
end

So basically, the

expect { click_button "Create my account" }.not_to change(User, :count)

is part RSpec:

expect {...}.not_to change(User, :count)

and part Capybara

click_button "Create my account"

(Here's a link to the Capyabara DSL -- you can search for click_button)

It sounds like you're looking for an overall example with them both. This isn't a perfect example, but it could look something like this:

describe "Tasks" do  
  describe "GET /tasks" do  
    it "displays tasks" do  
      expect { click_button "Create my account" }.not_to change(User, :count)
    end  
  end 
end 
like image 29
Kevin Bedell Avatar answered Oct 11 '22 03:10

Kevin Bedell