$30 off During Our Annual Pro Sale. View Details »

RSpec - Level Up

RSpec - Level Up

You've been using RSpec for a while, you know how to write tests, but you’re not sure you're getting the most from it. This is a quick run through of some of the lesser known, more advanced features of RSpec.

Jon Rowe

March 11, 2019
Tweet

More Decks by Jon Rowe

Other Decks in Programming

Transcript

  1. RSpec - Level Up
    @JonRowe
    (One of the) RSpec Maintainer(s)

    View Slide

  2. The Humble Example
    it "will do the correct thing" do
    expect(important_thing).to be_valid
    end

    View Slide

  3. The Context
    RSpec.describe do
    context "in most cases" do
    it "will do the correct thing" do
    end
    end
    end

    View Slide

  4. Hooks, metadata and filters
    • before(:example) / after(:example) / around(:example)
    • before(:context) / after(:context)
    • before(:suite) / after(:suite)

    View Slide

  5. Hooks, metadata and filters
    • Metadata is a hash of information about an example
    RSpec.describe "Athing", this_is_metadata: "Hi!" do
    context this_is_metadata: "I'll override the top one" do
    end
    it also_has_meta_data: "I'll have both keys" do
    end
    end

    View Slide

  6. Hooks, metadata and filters
    • Metadata can be used to run certain subsets of specs via
    filtering
    • rspec --tags type:awesome

    View Slide

  7. Hooks, metadata and filters
    • Metadata can be created by aliases.
    RSpec.configure do |c|
    c.define_example_group_method :fdescribe, focus: true
    end

    View Slide

  8. Hooks, metadata and filters
    • Metadata can be conditionally filtered upon:
    RSpec.configure do |c|
    c.filter_run_when_matching :focus
    end

    View Slide

  9. Hooks, metadata and filters
    • Metadata can be used to conditionally include code, or run
    hooks
    RSpec.configure do |config|
    config.include SomeModule, type: :awesome
    config.before :example, type: :awesome do
    # all the things
    end
    end

    View Slide

  10. Hooks, metadata and filters
    • Can be used to apply ordering
    RSpec.configure do |config|
    config.register_ordering :global do |examples|
    acceptance, unit = examples.partition { |ex| ex.metadata[:acceptance] }
    unit.shuffle + acceptance.shuffle
    end
    end

    View Slide

  11. Hooks, metadata and filters
    • Can be used to apply ordering to specific things
    RSpec.configure do |config|
    config.register_ordering :ordered_subset do |examples|
    examples
    end
    end

    View Slide

  12. Hooks, metadata and filters
    • Used by rspec-rails to apply the different methods to examples
    • Built in configuration such as hooks:
    • skip: "Reason"
    • pending: "Reason"
    • :aggregate_failures

    View Slide

  13. Aggregate Failures
    it "wraps failures together", :aggregate_failures do
    expect(response.status).to eq(200)
    expect(response.headers).to include("Content-Type" => "application/json")
    expect(response.body).to eq('{"message":"Success"}')
    end

    View Slide

  14. Aggregate Failures
    it "wraps failures together" do
    aggregate_failures "testing response" do
    expect(response.status).to eq(200)
    expect(response.headers).to include("Content-Type" => "application/json")
    expect(response.body).to eq('{"message":"Success"}')
    end
    end

    View Slide

  15. Aggregate Failures
    1) wraps failures together
    Got 3 failures:
    1.1) Failure/Error: expect(response.status).to eq(200)
    expected: 200
    got: nil
    (compared using ==)
    # ./spec/aggregate_failures_spec.rb:12:in `block (2 levels) in '
    1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
    expected nil to include {"Content-Type" => "application/json"}, but it does not respond to `include?`
    # ./spec/aggregate_failures_spec.rb:13:in `block (2 levels) in '
    1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
    expected: "{\"message\":\"Success\"}"
    got: nil
    (compared using ==)
    # ./spec/aggregate_failures_spec.rb:14:in `block (2 levels) in '

    View Slide

  16. One expectation per test?
    • How do we express complex expectations as one expectation
    • Compound matchers combine multiple assertions into one
    expectation
    • Consider an acceptance test that is checking a JSON response
    • Do we hard code a massive expected JSON response?

    View Slide

  17. Compound Matchers
    • With compound matchers we can express "complex"
    requirements easily
    expect(my_json).to match a_hash_including(
    'something' => array_including(hash_including(id: 1))
    )

    View Slide

  18. Compound Matchers
    • Matchers are reusable objects
    • Thus they present an easy way to create custom matchers
    def have_detail fragment
    a_hash_including(
    'something' => array_including(hash_including(fragment))
    )
    end

    View Slide

  19. Compound Matchers
    • You can combine argument matchers in this fashion
    • You can combine normal matchers using and and/or or
    • Any matcher supporting values_match? and ===
    expect(
    my_json
    ).to have_detail(id: 1).and have_detail(id: 2)

    View Slide

  20. Compound Matchers
    • Exception to the rule, negative matchers.
    • Instead create a "negated" matcher alias
    RSpec::Matchers.define_negated_matcher :preserve, :change
    expect {
    object.action
    }.to have_detail(id: 1).and preserve { object.value }

    View Slide

  21. Custom Matchers
    • You can alias matchers using RSpec::Matchers.alias_matcher
    • You can negate them using
    RSpec::Matchers.define_negated_matcher
    • But what if you want to create your own from scratch?

    View Slide

  22. Custom Matchers
    RSpec::Matchers.define :matcher_name do |expected|
    match do |actual|
    # ... logic
    end
    end

    View Slide

  23. Custom Matchers
    RSpec::Matchers.define :matcher_name do |expected|
    supports_block_expectations # actual will be a block
    diffable # failed matchers will attempt to diff expected / actual
    description { "..." }
    failure_message { "..." }
    failure_message_when_negated { "..." }
    chain :another_thing, { "..." } # new, allows you to chain multiple matchs
    end

    View Slide

  24. Custom Matchers
    • Are just Ruby
    • Require matches? and failure_message to be defined.
    • Optionally diffable? (requiring expected and actual)
    • Optionally supports_block_expectations?
    expects_call_stack_jump?

    View Slide

  25. What do we do when we have failures?
    • Lots of customisation on running the suite, what about after?

    View Slide

  26. Bisect
    • Allows you to find the minimum failure set for a seed.
    • rspec --seed 1234 --bisect
    • Has both a shell and a fork runner.

    View Slide

  27. Bisect
    Bisect started using options: "--seed 1234"
    Running suite to find failures... (0.17102 seconds)
    Starting bisect with 1 failing example and 9 non-failing examples.
    Checking that failure(s) are order-dependent... failure appears to be order-dependent
    Round 1: bisecting over non-failing examples 1-9 .. ignoring examples 6-9 (0.32943 seconds)
    Round 2: bisecting over non-failing examples 1-5 .. ignoring examples 4-5 (0.3154 seconds)
    Round 3: bisecting over non-failing examples 1-3 .. ignoring example 3 (0.2175 seconds)
    Bisect aborted!
    The most minimal reproduction command discovered so far is:

    View Slide

  28. Formatters
    • Formatters run all of RSpecs reporting.
    • Can build entirely custom implementations
    • Hook into the lifecycle of the test suite.
    • Subscribe to only the events you need

    View Slide

  29. Formatters
    class MyTotallyAmazingFormatter
    RSpec::Core::Formatters.register self, :example_failed, :example_passed
    def initialize output
    @output = output
    end
    def example_failed notification
    @output.puts "Bad times baz, we broke the build."
    end
    def example_passed notification
    @output.puts "Good news everyone!"
    end
    end

    View Slide

  30. Thanks
    @JonRowe

    View Slide