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?
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:
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 }
.click_button "Create my account"
which is a Capybara method that simulates a mouse click on a link.User.count
again.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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With