RSpec

Kerry Buckley
IPRUG, 4 January 2011

Creative Commons License

Why RSpec?

Why RSpec?

Clear, concise and readable

            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
          

Why RSpec?

Documentation

          Stack
            when new
              should be empty
              should have 0 items

          Finished in 0.00089 seconds
          2 examples, 0 failures
        

Gems

Basics

          describe "Rain" do
            it "is wet" do
              ...
            end
          end
        

specify can also be used in place of it.

Contexts

          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.

Describing methods

        describe MyClass do
          describe ".class_method" do
            ...
          end

          describe "#instance_method" do
            ...
          end
        end
      

Documentation output:

        Myclass.class_method
          ...
        Myclass#instance_method
          ...
      

Running

Formatters

progress (default)

          $ rspec --colour spec/
          ..

          Finished in 0.00089 seconds
          2 examples, 0 failures
        

A digression

Configuration file

We don’t want to type --colour every time!

Put common options in .rspec in the project root.

Formatters

documentation

          $ rspec -f doc spec/

          Stack
            when new
              should be empty
              should have 0 items

          Finished in 0.00089 seconds
          2 examples, 0 failures
        

Formatters

html

HTML output

Formatters

Fuubar

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
        

Formatters

Multiple formatters

          $ rspec -f Fuubar -f html -o specs.html spec/
        

Profiling

        $ 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
          ...
      

Pending specs

        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
      

Pending specs

        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
      

Pending specs

        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
      

Running one spec

          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
        

Filtering by tag

          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 ...
        

Filtering by tag value

          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
        

Implicit filtering

          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
        

Filtering by name

        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
      

Setup and teardown

          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
        

Expectations

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.

Built-in matchers

Equality

          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)    #
        

Built-in matchers

Floating point

          Math::PI.should be_within(0.01).of(22.0/7)
        

Built-in matchers

True or false

          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.

Built-in matchers

Operators

          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
        

Built-in matchers

Collections

        # 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]
      

Built-in matchers

Strings

          "This is a string".should include "str"

          # These two are identical:
          "This is a string".should =~ /^This/
          "This is a string".should match(/^This/)
        

Another digression

Failure messages

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]
        

Another digression

Failure messages

…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).

Back to built-in matchers

Predicates

            [].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)
          

Built-in matchers

Classes

          "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
        

Built-in matchers

Changing values

          expect{ array << 42 }.to change{ array.size }.from(0).to(1)

          expect{ array << 42 }.to change{ array.size }.by(1)
        

Built-in matchers

Raising and throwing

          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)
        

Built-in matchers

Exist

          obj.should exist # passes if obj.exist? returns true
        

This is the only use case I could think of:

          Pathname("/etc/passwd").should exist
        

Built-in matchers

Arbitrary blocks

          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…

Custom matchers

        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
      

Hang on a minute…

        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?

Implicit subject

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
      

Implicit subject

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...

Explicit subject

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
      

Custom matchers

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
      

Custom matchers

Not all matchers take an argument:

        RSpec::Matchers.define :be_a_multiple_of_3 do
          match do |number|
            number % 3 == 0
          end
        end
      

Shoulda matchers

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) }
      

Speaking of shoulda…

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

Let there be let

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
      

Let there be let

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
      

Let there be let

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.

Shared 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
      

Shared examples

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
      

Mocks and stubs

Simple stub

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)
      

Mocks and stubs

Stubbing methods

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
      

Mocks and stubs

Stubbing methods

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
      

Mocks and stubs

Null object

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)
      

Mocks and stubs

Stubbing a method chain

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
      

Mocks and stubs

Expecting method calls

        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
      

Mocks and stubs

Expecting calls with arguments

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

Mocks and stubs

More mockery

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"
      

Mocks and stubs

Method order

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
      

Mocks and stubs

Playing in irb

If you want to experiment with mocks and stubs in irb:

        require "rspec/mocks/standalone"
      

Rails

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
      

Rails models

Fake ActiveRecord objects are useful in controller specs:

        article = mock_model Article, :text => "Hello world"
      

Testing errors:

        widget.should have(1).error_on(:name)
      

Rails controllers

View integration

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
      

Rails controllers

Anonymous controller

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
      

Rails helpers

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
      

Rails helpers

Accessing other helpers

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
      

Rails routing

        { :get => "/" }.should route_to(:controller => "welcome")

        { :get => "/widgets" }.should_not be_routable

        {:get => new_widget_path}.should route_to(
          :controller => "widgets", :action => "new")
      

Learning more

The RSpec Book

The RSpec Book

Learning more

http://relishapp.com/rspec

relishapp

Learning more

https://github.com/rspec

github

Thank you!

Any questions?