Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

A Testing Tool for Ruby “Designed to make Test-Driven Development a productive and enjoyable experience…” An alternative to MiniTest / Test::Unit

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

1 describe "Equality" do 2 it "should verify the obvious" do 3 ¦ expect(5).to eql(5) 4 end 5 end

Slide 5

Slide 5 text

expect(actual).to matcher(expected) expect(actual).not_to matcher(expected) expect(5).to eq(5) expect(5).not_to eq(4)

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

[bhughes@redatom:penguins]$ rspec --init create .rspec create spec/spec_helper.rb [bhughes@redatom:penguins]$ cat .rspec --color --warnings --require spec_helper

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

[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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

[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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

[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

Slide 22

Slide 22 text

New Expectations Syntax Why?

Slide 23

Slide 23 text

# Should Syntax foo.should eq(bar) foo.should_not eq(bar) # Expect Syntax expect(foo).to eq(bar) expect(foo).not_to eq(bar)

Slide 24

Slide 24 text

1 RSpec.configure do |config| 2 config.expect_with :rspec do |expectations| 3 ¦ expectations.syntax = [:expect, :should] 4 end 5 end Use Both Syntaxes

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

New “all” Matcher expect([1, 3, 5]).to all(be_odd)

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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)

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Questions? Ben Hughes | http://benhughes.name | @rubiety