Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Review of RSpec 3

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.

Ben Hughes

August 08, 2014
Tweet

More Decks by Ben Hughes

Other Decks in Technology

Transcript

  1. A Review of

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

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

    View Slide

  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)

    View Slide

  9. [[email protected]:penguins]$ rspec --init
    create .rspec
    create spec/spec_helper.rb
    [[email protected]:penguins]$ cat .rspec
    --color
    --warnings
    --require spec_helper

    View Slide

  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

    View Slide

  11. [[email protected]: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

    View Slide

  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

    View Slide

  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

    View Slide

  14. [[email protected]: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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  21. [[email protected]: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

    View Slide

  22. New Expectations Syntax
    Why?

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

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

    View Slide