A Review of RSpec 3

92772ff5353c89d9bd10f8e334161e16?s=47 Ben Hughes
August 08, 2014

A Review of RSpec 3

A brief introduction to RSpec, and a review of the various new features/changes in RSpec version 3.

92772ff5353c89d9bd10f8e334161e16?s=128

Ben Hughes

August 08, 2014
Tweet

Transcript

  1. A Review of
 RSpec 3 Ben Hughes | http://benhughes.name |

    @rubiety
  2. A Testing Tool for Ruby “Designed to make Test-Driven Development

    a productive and enjoyable experience…” An alternative to MiniTest / Test::Unit
  3. A Testing Tool for Ruby • rspec: 
 a rich

    command-line program • rspec-core: 
 textual descriptions of examples & groups, flexible & customizable reporting • rspec-expectations: 
 extensible expectation language • rspec-mocks: 
 mocking/stubbing framework
  4. 1 describe "Equality" do 2 it "should verify the obvious"

    do 3 ¦ expect(5).to eql(5) 4 end 5 end
  5. expect(actual).to matcher(expected) expect(actual).not_to matcher(expected) expect(5).to eq(5) expect(5).not_to eq(4)

  6. # Comparisons expect(actual).to be > expected expect(actual).to be < expected

    expect(actual).to be_between(minimum, maximum).inclusive expect(actual).to be_between(minimum, maximum).exclusive expect(actual).to match(/expression/) expect(actual).to be_within(delta).of(expected) expect(actual).to start_with expected expect(actual).to end_with expected # Types/classes/response expect(actual).to be_instance_of(expected) expect(actual).to be_kind_of(expected) expect(actual).to respond_to(expected)
  7. # Truthiness and existentialism expect(actual).to be_truthy # passes if actual

    is truthy (not nil or false) expect(actual).to be true # passes if actual == true expect(actual).to be_falsey # passes if actual is falsy (nil or false) expect(actual).to be false # passes if actual == false expect(actual).to be_nil # passes if actual is nil # Expecting errors expect { do_something }.to raise_error expect { do_something }.to raise_error(ErrorClass) expect { do_something }.to raise_error("message") expect { do_something }.to raise_error(ErrorClass, "message")
  8. # Collection / Hash Membership expect([1, 2, 3]).to include(1) expect([1,

    2, 3]).to include(1, 2) expect(:a => 'b').to include(:a => 'b') expect("this string").to include("is str") expect([1, 2, 3]).to contain_exactly(2, 1, 3) expect([1, 2, 3]).to match_array([3, 2, 1]) # Change observation expect { a += 1 }.to change { a }.by(1) expect { a += 3 }.to change { a }.from(2) expect { a += 3 }.to change { a }.by_at_least(2)
  9. [bhughes@redatom:penguins]$ rspec --init create .rspec create spec/spec_helper.rb [bhughes@redatom:penguins]$ cat .rspec

    --color --warnings --require spec_helper
  10. 1 RSpec.configure do |config| 2 config.order = :random 3 4

    config.expect_with :rspec do |expectations| 5 ¦ expectations.syntax = :expect 6 end 7 8 config.mock_with :rspec do |mocks| 9 ¦ mocks.syntax = :expect 10 end 11 end
  11. [bhughes@redatom:penguins]$ rspec spec No examples found. ! ! Finished in

    0.00036 seconds (files took 0.14421 seconds to load) 0 examples, 0 failures ! Randomized with seed 43262
  12. 1 class Calculator 2 def add(first, second) 3 ¦ first

    + second 4 end 5 6 def multiply(first, second) 7 ¦ first * second 8 end 9 10 def divide(first, second) 11 ¦ first / second 12 end 13 end
  13. 1 describe "Calculator" do 2 it "should #add numbers 2

    and 3 to get 5" do 3 ¦ calculator = Calculator.new 4 ¦ expect(calculator.add(2, 3)).to eql(5) 5 end 6 7 it "should #add numbers -2 and 3 to get 1" do 8 ¦ calculator = Calculator.new 9 ¦ expect(calculator.add(-2, 3)).to eql(1) 10 end 11 12 it "should #multiply 2 and 3 to get 6" do 13 ¦ calculator = Calculator.new 14 ¦ expect(calculator.multiply(2, 3)).to eql(6) 15 end 16 17 it "should #multiply -2 and 3 to get -6" do 18 ¦ calculator = Calculator.new 19 ¦ expect(calculator.multiply(-2, 3)).to eql(-6) 20 end 21 22 it "should #divide 6 and 2 to get 3" do 23 ¦ calculator = Calculator.new 24 ¦ expect(calculator.divide(6, 2)).to eql(3) 25 end 26 27 it "should #divide 3 and 0 to raise ZeroDivisionError" do 28 ¦ calculator = Calculator.new 29 ¦ expect { calculator.divide(3, 0) }.to raise_error(ZeroDivisionError) 30 end 31 end
  14. [bhughes@redatom:scratch/calculator]$ rspec spec ! Calculator should #multiply 2 and 3

    to get 6 should #divide 6 and 2 to get 3 should #add numbers -2 and 3 to get 1 should #divide 3 and 0 to raise ZeroDivisionError should #add numbers 2 and 3 to get 5 ! Finished in 0.00176 seconds (files took 0.09221 seconds to load) 5 examples, 0 failures
  15. 1 describe "Calculator" do 2 before { @calculator = Calculator.new

    } 3 4 it "should #add numbers 2 and 3 to get 5" do 5 ¦ expect(@calculator.add(2, 3)).to eql(5) 6 end 7 8 it "should #add numbers -2 and 3 to get 1" do 9 ¦ expect(@calculator.add(-2, 3)).to eql(1) 10 end 11 12 it "should #multiply 2 and 3 to get 6" do 13 ¦ expect(@calculator.multiply(2, 3)).to eql(6) 14 end 15 16 it "should #multiply -2 and 3 to get -6" do 17 ¦ expect(@calculator.multiply(-2, 3)).to eql(-6) 18 end 19 20 it "should #divide 6 and 2 to get 3" do 21 ¦ expect(@calculator.divide(6, 2)).to eql(3) 22 end 23 24 it "should #divide 3 and 0 to raise ZeroDivisionError" do 25 ¦ expect { @calculator.divide(3, 0) }.to raise_error(ZeroDivisionError) 26 end 27 end
  16. 1 describe "Calculator" do 2 let(:calculator) { Calculator.new } 3

    4 it "should #add numbers 2 and 3 to get 5" do 5 ¦ expect(calculator.add(2, 3)).to eql(5) 6 end 7 8 it "should #add numbers -2 and 3 to get 1" do 9 ¦ expect(calculator.add(-2, 3)).to eql(1) 10 end 11 12 it "should #multiply 2 and 3 to get 6" do 13 ¦ expect(calculator.multiply(2, 3)).to eql(6) 14 end 15 16 it "should #multiply -2 and 3 to get -6" do 17 ¦ expect(calculator.multiply(-2, 3)).to eql(-6) 18 end 19 20 it "should #divide 6 and 2 to get 3" do 21 ¦ expect(calculator.divide(6, 2)).to eql(3) 22 end 23 24 it "should #divide 3 and 0 to raise ZeroDivisionError" do 25 ¦ expect { calculator.divide(3, 0) }.to raise_error(ZeroDivisionError) 26 end 27 end
  17. 1 describe "Calculator" do 2 let!(:calculator) { Calculator.new } 3

    4 it "should #add numbers 2 and 3 to get 5" do 5 ¦ expect(calculator.add(2, 3)).to eql(5) 6 end 7 8 it "should #add numbers -2 and 3 to get 1" do 9 ¦ expect(calculator.add(-2, 3)).to eql(1) 10 end 11 12 it "should #multiply 2 and 3 to get 6" do 13 ¦ expect(calculator.multiply(2, 3)).to eql(6) 14 end 15 16 it "should #multiply -2 and 3 to get -6" do 17 ¦ expect(calculator.multiply(-2, 3)).to eql(-6) 18 end 19 20 it "should #divide 6 and 2 to get 3" do 21 ¦ expect(calculator.divide(6, 2)).to eql(3) 22 end 23 24 it "should #divide 3 and 0 to raise ZeroDivisionError" do 25 ¦ expect { calculator.divide(3, 0) }.to raise_error(ZeroDivisionError) 26 end 27 end
  18. 1 describe "Calculator" do 2 subject { Calculator.new } 3

    4 it "should #add numbers 2 and 3 to get 5" do 5 ¦ expect(subject.add(2, 3)).to eql(5) 6 end 7 8 it "should #add numbers -2 and 3 to get 1" do 9 ¦ expect(subject.add(-2, 3)).to eql(1) 10 end 11 12 it "should #multiply 2 and 3 to get 6" do 13 ¦ expect(subject.multiply(2, 3)).to eql(6) 14 end 15 16 it "should #multiply -2 and 3 to get -6" do 17 ¦ expect(subject.multiply(-2, 3)).to eql(-6) 18 end 19 20 it "should #divide 6 and 2 to get 3" do 21 ¦ expect(subject.divide(6, 2)).to eql(3) 22 end 23 24 it "should #divide 3 and 0 to raise ZeroDivisionError" do 25 ¦ expect { subject.divide(3, 0) }.to raise_error(ZeroDivisionError) 26 end 27 end
  19. 1 describe Calculator do 2 it "should #add numbers 2

    and 3 to get 5" do 3 ¦ expect(subject.add(2, 3)).to eql(5) 4 end 5 6 it "should #add numbers -2 and 3 to get 1" do 7 ¦ expect(subject.add(-2, 3)).to eql(1) 8 end 9 10 it "should #multiply 2 and 3 to get 6" do 11 ¦ expect(subject.multiply(2, 3)).to eql(6) 12 end 13 14 it "should #multiply -2 and 3 to get -6" do 15 ¦ expect(subject.multiply(-2, 3)).to eql(-6) 16 end 17 18 it "should #divide 6 and 2 to get 3" do 19 ¦ expect(subject.divide(6, 2)).to eql(3) 20 end 21 22 it "should #divide 3 and 0 to raise ZeroDivisionError" do 23 ¦ expect { subject.divide(3, 0) }.to raise_error(ZeroDivisionError) 24 end 25 end
  20. 1 describe Calculator do 2 describe "addition" do 3 ¦

    it "should #add numbers 2 and 3 to get 5" do 4 ¦ ¦ expect(subject.add(2, 3)).to eql(5) 5 ¦ end 6 7 ¦ it "should #add numbers -2 and 3 to get 1" do 8 ¦ ¦ expect(subject.add(-2, 3)).to eql(1) 9 ¦ end 10 end 11 12 describe "multiplication" do 13 ¦ it "should #multiply 2 and 3 to get 6" do 14 ¦ ¦ expect(subject.multiply(2, 3)).to eql(6) 15 ¦ end 16 17 ¦ it "should #multiply -2 and 3 to get -6" do 18 ¦ ¦ expect(subject.multiply(-2, 3)).to eql(-6) 19 ¦ end 20 end 21 22 describe "division" do 23 ¦ it "should #divide 6 and 2 to get 3" do 24 ¦ ¦ expect(subject.divide(6, 2)).to eql(3) 25 ¦ end 26 27 ¦ it "should #divide 3 and 0 to raise ZeroDivisionError" do 28 ¦ ¦ expect { subject.divide(3, 0) }.to raise_error(ZeroDivisionError) 29 ¦ end 30 end 31 end
  21. [bhughes@redatom:scratch/calculator]$ rspec spec ! Calculator division should #divide 6 and

    2 to get 3 should #divide 3 and 0 to raise ZeroDivisionError addition should #add numbers 2 and 3 to get 5 should #add numbers -2 and 3 to get 1 multiplication should #multiply 2 and 3 to get 6 should #multiply -2 and 3 to get -6 ! Finished in 0.00211 seconds (files took 0.09233 seconds to load) 6 examples, 0 failures
  22. New Expectations Syntax Why?

  23. # Should Syntax foo.should eq(bar) foo.should_not eq(bar) # Expect Syntax

    expect(foo).to eq(bar) expect(foo).not_to eq(bar)
  24. 1 RSpec.configure do |config| 2 config.expect_with :rspec do |expectations| 3

    ¦ expectations.syntax = [:expect, :should] 4 end 5 end Use Both Syntaxes
  25. 1 describe MyClass do 2 before(:each) { puts example.metadata }

    3 end 4 5 describe MyClass do 6 before(:example) {|example| puts example.metadata } 7 let(:example_description) {|example| example.description } 8 9 it "accesses the example" do |example| 10 ¦ puts example.metadata 11 end 12 end Example Semantics
  26. Pending Semantics 1 describe Penguin do 2 # This will

    fail! When using pending, test must fail now. 3 it "should do something" do 4 ¦ pending 5 ¦ expect(true).to eq(true) 6 end 7 8 skip "not implemented yet" do 9 end 10 11 it "does something", :skip => true do 12 end 13 14 it "does something", :skip => "reason explanation" do 15 end 16 17 it "does something else" do 18 ¦ skip "reason explanation" 19 end 20 end
  27. Compound Matchers 1 # Originally... 2 expect(alphabet).to start_with("a") 3 expect(alphabet).to

    end_with("z") 4 5 # Now, can be... 6 expect(alphabet).to start_with("a").and end_with("z") 7 8 # You can also use "or"... 9 expect(stoplight.color).to eq("red").or eq("green")
  28. Composable Matchers string = "food" expect { string = "barn"

    }.to change { string }. from(a_string_matching(/foo/)). to(a_string_matching(/bar/)) ! object = [ { klass: "Class1", id: 1, other: :foo }, { klass: "Class2", id: 2, other: :bar } ] expect(object).to match [ a_hash_including(klass: "Class1", id: 37), a_hash_including(klass: "Class2", id: 42) ]
  29. Composable Matchers hash = { a: { b: ["foo", 5],

    c: { d: 2.05 } } } expect(hash).to match( a: { b: a_collection_containing_exactly( an_instance_of(Fixnum), a_string_starting_with("f") ), c: { d: (a_value < 3) } } )
  30. New “all” Matcher expect([1, 3, 5]).to all(be_odd)

  31. New “between” Matcher 1 # like `Comparable#between?`, it is inclusive

    by default 2 expect(10).to be_between(5, 10) 3 4 # ...but you can make it exclusive: 5 expect(10).not_to be_between(5, 10).exclusive 6 7 # ...or explicitly label it inclusive: 8 expect(10).to be_between(5, 10).inclusive
  32. match_array / contain_exactly # Should syntax: [2, 1, 3].should =~

    [1, 2, 3] # Expect syntax: expect([2, 1, 3]).to match_array([1, 2, 3]) expect([2, 1, 3]).to contain_exactly(1, 2, 3) # Same
  33. New “output” Matcher expect { print "foo" }.to output("foo").to_stdout expect

    { print "foo" }.to output(/fo/).to_stdout expect { warn "bar" }.to output(/bar/).to_stderr
  34. rspec-mocks Syntax 1 # old syntax: 2 object.stub(foo: 1, bar:

    2) 3 4 # new syntax: 5 allow(object).to receive_messages(foo: 1, bar: 2) 6 7 # also works with expect: 8 expect(object).to receive_messages(foo: 1, bar: 2)
  35. Other Changes • Removed support for Ruby 1.8.6 / 1.9.1

    • Better Ruby 2 support (keyword arguments, prepended modules) • Internal refactoring & housekeeping: breaking out to other gems • Completely new RSpec formatter (rspec-legacy_formatters still available) • Can disable exposing DSL globally (need to use RSpec.describe):
 config.expose_dsl_globally = false • Example group aliases:
 config.alias_example_group_to :describe_model, :type => :model • Skipped group aliases: xdescribe, xcontext, xit • Focused group aliases: fdescribe, fcontext, fit
  36. Questions? Ben Hughes | http://benhughes.name | @rubiety