class StackTest < Test::Unit def setup @stack = Stack.new end def test_new_is_empty assert_equal true, @stack.empty? end def test_new_has_size_0 assert_equal 0, @stack.size end end
vs
describe Stack do context "when new" do it { should be_empty } it { should have(0).items } end end
Stack when new should be empty should have 0 items Finished in 0.00089 seconds 2 examples, 0 failures
describe "Rain" do it "is wet" do ... end end
specify
can also be used in place of it
.
describe "Rain" do context "in the summer" do it "is refreshing" do ... end end context "in the winter" do it "is unpleasant" do ... end end end
context
is an alias for describe
.
Use whichever is most appropriate.
describe MyClass do describe ".class_method" do ... end describe "#instance_method" do ... end end
Documentation output:
Myclass.class_method ... Myclass#instance_method ...
$ rspec --colour spec/ .. Finished in 0.00089 seconds 2 examples, 0 failures
We don’t want to type --colour
every time!
Put common options in .rspec
in the project root.
$ rspec -f doc spec/ Stack when new should be empty should have 0 items Finished in 0.00089 seconds 2 examples, 0 failures
Fuubar is an external formatter (gem in fuubar
).
$ rspec -f Fuubar spec/ 1) Stack after pushing 1 item Failure/Error: it { should have(1).item } Expected 1 item, got 0 # ./spec/stack_spec.rb:12 8/10: 80% |================================ | ETA: 00:00:02
$ rspec -f Fuubar -f html -o specs.html spec/
$ rspec --profile spec/ 55/55: 100% |====================================| Time: 00:00:00 Top 10 slowest examples: RedRock server allows the port to be overridden 0.10258 seconds ./spec/redrock_spec.rb:107 RedRock supports specification of a response body as an IO object 0.08752 seconds ./spec/redrock_spec.rb:336 RedRock returning a custom response returns the specified headers 0.07319 seconds ./spec/redrock_spec.rb:331 ...
describe "Something" do it "does something" do ... end it "does something I haven't implemented yet" end
Something ... does something I haven't implemented yet (PENDING: Not Yet Implemented) Pending: Something does something I haven't implemented yet # Not Yet Implemented # ./spec/something_spec.rb:5 Finished in 0.00052 seconds 2 examples, 0 failures, 1 pending
describe "Something" do ... it "does something I haven't implemented yet" do pending "waiting for inspiration" ... end end
Something ... does something I haven't implemented yet (PENDING: waiting for inspiration) Pending: Something does something I haven't implemented yet # waiting for inspiration # ./spec/something_spec.rb:5 Finished in 0.00078 seconds 2 examples, 0 failures, 1 pending
describe "Something" do before { pending "Coming soon!" } it "does something" do end it "does something else" do end end
Something does something (PENDING: Coming soon!) does something else (PENDING: Coming soon!) Pending: Something does something # Coming soon! # ./spec/something_spec.rb:4 Something does something else # Coming soon! # ./spec/something_spec.rb:7 Finished in 0.0114 seconds 2 examples, 0 failures, 2 pending
describe "Something" do it "does stuff" do ... it "does other stuff" do ... it "does one more thing" do ... end
$ rspec -f doc spec/something_spec.rb:5 Run filtered using {:line_number=>5} Something does other stuff Finished in 0.00036 seconds 1 example, 0 failures
describe "Something" do it "does stuff" do ... end it "does the thing I'm working on", :current => true do ... end end
Run only specs tagged ‘current’
rspec --tag current ...
Run specs not tagged ‘current’
rspec --tag ~current ...
describe "Something" do it "behaves one way in Ruby 1.8", :ruby => "1.8" do ... end it "behaves another way in Ruby 1.9", :ruby => "1.9" do ... end end
Run 1.8 specs:
rspec --tag ruby:1.8
RUBY_1_9 = (RUBY_VERSION =~ /^1\.9/) describe "Something" do it "does this when using 1.9", :if => RUBY_1_9 do ... end it "Does that when not using 1.9", :unless => RUBY_1_9 do ... end end
describe "Something" do it "does stuff" do ... it "does other stuff" do ... it "does one more thing" do ... end
Select specs using a regular expression:
$ rspec -e stuff spec/something_spec.rb Run filtered using {:full_description=>/(?-mix:stuff)/} Something does stuff does other stuff Finished in 0.00101 seconds 2 examples, 0 failures
describe "Something" do before :all do # per-context setup end before do # per-example setup end after do # per-example teardown end after :all do # per-context teardown end end
RSpec adds should
and should_not
to all objects.
(2 + 2).should == 4 "foo".should_not == "bar" value.should be_true
The ==
and be_true
are matchers.
a.should equal(b) # both pass if a.equal? b a.should be(b) # a.should eql(b) # passes if a.eql? b a.should == b # both pass if a == b a.should eq(b) #
Math::PI.should be_within(0.01).of(22.0/7)
obj.should be_true # passes if obj is truthy (not nil or false) obj.should be_false # passes if obj is falsy (nil or false) obj.should be_nil # passes if obj is nil obj.should be # passes if obj is not nil
should be
is preferred to should_not be_nil
.
7.should == 7 [1, 2, 3].should == [1, 2, 3] "this is a string".should =~ /^this/ "this is a string".should_not =~ /^that/ String.should === "this is a string"
Comparison operators work with the "be" matcher:
37.should be < 100 37.should be <= 38 37.should be >= 2 37.should be > 7
# These two are identical: [1, 2, 3].should have(3).items [1, 2, 3].should have_exactly(3).items [1, 2, 3].should have_at_least(2).items [1, 2, 3].should have_at_most(4).items # "items" is just syntactic sugar: [1, 2, 3].should have(3).numbers [1, 2, 3].should include(2) [1, 2, 3].should include(1, 2) {:a => 1, :b => 2}.should include(:a => 1) [1, 2, 3].should =~ [2, 3, 1] [:a, :c, :b].should_not =~ [:a, :c]
"This is a string".should include "str" # These two are identical: "This is a string".should =~ /^This/ "This is a string".should match(/^This/)
The default messages are usually pretty good:
(2 + 2).should == 5 [1, 2, 3].should =~ [4, 3, 2]
Failure/Error: (2 + 2).should == 5
expected: 5
got: 4 (using ==)
Failure/Error: [1, 2, 3].should =~ [4, 3, 2]
expected collection contained: [2, 3, 4]
actual collection contained: [1, 2, 3]
the missing elements were: [4]
the extra elements were: [1]
…but you can use a custom message if you like*:
it "includes 3" do [1, 2, 3].should include(3), "Oh noes! No three!" end
1) Something includes 3
Failure/Error: [1, 2, 4].should include(3), "Oh noes! No three!"
Oh noes! No three!
*except with operator matchers (==
etc).
[].empty?.should be_true 42.even?.should be_true 123.multiple_of?(5). should be_false {:a => 1, :b => 2}.has_key?(:a). should be_true
➧
[].should be_empty 42.should be_even 123.should_not be_multiple_of(5) {:a => 1, :b => 2}. should have_key(:a)
"foo".should be_an_instance_of(String) # Exact class "foo".should_not be_an_instance_of(Object) # "foo".should be_a_kind_of(Object) # Match subclasses "foo".should be_an(Object) # "foo".should respond_to(:upcase) "foo".should respond_to(:upcase, :downcase) "foo".should respond_to(:upcase).with(0).arguments
expect{ array << 42 }.to change{ array.size }.from(0).to(1) expect{ array << 42 }.to change{ array.size }.by(1)
expect { 4/2 }.to_not raise_error expect { 4/0 }.to raise_error expect { 4/0 }.to raise_error(ZeroDivisionError) expect { 4/0 }.to raise_error(ZeroDivisionError, "divided by 0") expect { throw :foo }.to throw_symbol expect { throw :foo }.to throw_symbol(:foo) expect { throw :foo, 7 }.to throw_symbol(:foo, 7)
obj.should exist # passes if obj.exist? returns true
This is the only use case I could think of:
Pathname("/etc/passwd").should exist
10.should satisfy { |v| v % 5 == 0 }
Not really recommended, because of the unfriendly failure message:
Failure/Error: 11.should satisfy { |v| v % 5 == 0 } expected 11 to satisfy block
Which brings us to…
RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual % expected == 0 end end describe 10 do it { should be_a_multiple_of(5) } end
10 should be a multiple of 5 Finished in 0.04768 seconds 1 example, 0 failures
RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual % expected == 0 end end describe 10 do it { should be_a_multiple_of(5) } end
10 should be a multiple of 5 Finished in 0.04768 seconds 1 example, 0 failures
Firstly, note how a description has been automatically generated for us.
But what are we calling should
on?
The argument to describe
becomes the subject:
describe 42 do it { should be_even } it { should_not be_zero } end
If it’s a class, the subject is a new instance:
describe Array do it { should be_empty } it { should have(0).items } end
Access attributes of the subject with its
:
describe [1, 2, 3, 3] do its(:size) { should == 4 } its("uniq.size") { should == 3 } end
1233 size should == 4 uniq.size should == 3 Finished in 0.00279 seconds 2 examples, 0 failures
That description (from to_s
) could be better...
You can set the subject explicitly using a block:
describe "An array containing 1, 2, 3, 3" do subject { [1, 2, 3, 3] } its(:size) { should == 4 } end
And access it in examples as subject
:
describe Array do it "is empty" do subject.should be_empty end end
You can override the description and failure messages:
RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual % expected == 0 end failure_message_for_should do |actual| "expected that #{actual} would be a multiple of #{expected}" end failure_message_for_should_not do |actual| "expected that #{actual} would not be a multiple of #{expected}" end description do "be multiple of #{expected}" end end
Not all matchers take an argument:
RSpec::Matchers.define :be_a_multiple_of_3 do match do |number| number % 3 == 0 end end
Shoulda provides a whole bunch of Rails matchers, including:
it { should render_template(:show) } it { should assign_to(:user).with(@user) } it { should redirect_to(users_path) } it { should render_with_layout(:special) } it { should respond_with(:success) } it { should route(:get, "/posts/new").to(:action => :new) } it { should set_the_flash.to(/created/i) } it { should validate_presence_of(:name) } it { should_not allow_mass_assignment_of(:password) } it { should have_one(:profile) } it { should have_db_column(:salary).of_type(:decimal) }
Shoulda is focusing on the RSpec/Shoulda combination and will primarily support that combination of tools, moving away from Test::Unit.
Thoughtbot blog, June 2010
These examples have some duplication…
describe BowlingGame do it "scores all gutters with 0" do game = BowlingGame.new 20.times { game.roll(0) } game.score.should == 0 end it "scores all 1s with 20" do game = BowlingGame.new 20.times { game.roll(1) } game.score.should == 20 end end
We could move it into a before
block…
describe BowlingGame do before do @game = BowlingGame.new end it "scores all gutters with 0" do 20.times { @game.roll(0) } @game.score.should == 0 end it "scores all 1s with 20" do 20.times { @game.roll(1) } @game.score.should == 20 end end
But using let
is better.
describe BowlingGame do let(:game) { BowlingGame.new } it "scores all gutters with 0" do 20.times { game.roll(0) } game.score.should == 0 end it "scores all 1s with 20" do 20.times { game.roll(1) } game.score.should == 20 end end
Variables assigned by let
blocks are evaluated lazily,
memoised on first use, and reset between examples.
Factor out common examples:
shared_examples_for "a single-element array" do it { should_not be_empty } it { should have(1).element } end describe ["foo"] do it_behaves_like "a single-element array" end describe [42] do it_behaves_like "a single-element array" end
Variables can be set using a block:
shared_examples_for "a collection object" do describe "<<" do it "adds objects to the end of the collection" do collection << 1 collection << 2 collection.to_a.should eq([1,2]) end end end describe Array do it_behaves_like "a collection object" do let(:collection) { Array.new } end end describe Set do it_behaves_like "a collection object" do let(:collection) { Set.new } end end
A double is the generic term for mocks and stubs.
foo = double :foo, :size => 3, :to_s => "Foo" # ^^^^ foo is just a label foo.size # => 3 foo.to_s # => "Foo" foo.upcase # => RSpec::Mocks::MockExpectationError: Double "foo" # received unexpected message :upcase with (no args)
You can stub methods on doubles, or real objects or classes
obj.stub(:valid?).and_return true User.stub(:find).with(user_id).and_return user
Dynamic return value with a block:
obj.stub(:+) do |arg| obj + arg end
Successive calls can return different values:
foo.stub(:bar).and_return 1, 2, 3 foo.bar # => 1 foo.bar # => 2 foo.bar # => 3 foo.bar # => 3
If you want your double to respond to any message:
foo = double(:foo, :size => 3).as_null_object foo.upcase # => foo (returns self for unstubbed methods)
Often indicates law of demeter violations, but is useful for cases like named scopes, eg:
Article.recent.published
You could use multiple doubles:
Article.stub(:recent).and_return double(:published => articles)
But it’s easier to stub the whole chain at once:
Article.stub_chain(:recent, :published).and_return articles Article.stub_chain("recent.published").and_return articles
describe "Form" do let (:model) { double :model } let (:form) { Form.new model } context "when submitted" do it "saves the model" do model.should_receive(:save) form.submit end end end
Failures:
1) Form when submitted saves the model
Failure/Error: model.should_receive(:save)
(Double :model).save(any args)
expected: 1 time
received: 0 times
describe "Form" do let (:logger) { double :logger } let (:form) { Form.new logger } context "when submitted" do it "logs the event" do logger.should_receive(:info).with "Form submitted" form.submit end end end
Failures:
1) Form when submitted logs the event
Failure/Error: @logger.info "Form was submitted"
Double :logger received :info with unexpected arguments
expected: ("Form submitted")
got: ("Form was submitted")
You can specify call counts:
foo.should_receive(:bar).once foo.should_receive(:bar).at_least(3).times
Arguments can be less strict:
foo.should_receive(:bar).with(anything(), an_instance_of(String)) foo.should_receive(:bar).with(hash_including(:a => 1)) foo.should_receive(:bar).with(/^[a-z]*$/)
Mock methods can return values just like stubs:
foo.should_receive(:bar).and_return "baz"
Occasionally you need methods to be called in the right order:
it "logs the event" do logger.should_receive(:info).with(form.id).ordered logger.should_receive(:info).with("Form submitted").ordered form.submit end
Failures:
1) Form when submitted logs the event
Failure/Error: @logger.info "Form submitted"
Double :logger received :info out of order
If you want to experiment with mocks and stubs in irb:
require "rspec/mocks/standalone"
rspec-rails
is a drop-in replacement for Rails’s built-in testing.
Add it to your Gemfile
:
group :test, :development do gem "rspec-rails", "~> 2.4" end
Then run:
rails generate rspec:install
Fake ActiveRecord objects are useful in controller specs:
article = mock_model Article, :text => "Hello world"
Testing errors:
widget.should have(1).error_on(:name)
Views are stubbed by default. To render them, use render_views
:
describe WidgetsController do render_views describe "index" do it "renders the index template" do get :index response.should contain("Listing widgets") end end end
To test behaviour in ApplicationController
, you can define
actions on the fly in your spec:
describe ApplicationController do controller do def index raise ApplicationController::AccessDenied end end describe "handling AccessDenied exceptions" do it "redirects to the /401.html page" do get :index response.should redirect_to("/401.html") end end end
Access helper methods via the helper
object:
describe ApplicationHelper do describe "#page_title" do it "returns the value of @title" do assign(:title, "My Title") helper.page_title.should eql("My Title") end end end
If the helper under test calls methods in other helpers, you’ll need to include them in your spec:
describe SomeHelper do helper do include ApplicationHelper end ... end
{ :get => "/" }.should route_to(:controller => "welcome") { :get => "/widgets" }.should_not be_routable {:get => new_widget_path}.should route_to( :controller => "widgets", :action => "new")