Start ruby debugger if rspec test fails
Asked Answered
P

7

30

Often, when a test fails, I spend quite sometime trying to figure out the what caused it to fail. It'd be useful if RSpec could kick off a Ruby debugger when the test fails, so that I can inspect the local variables immediately to drill down on the cause.

The work-around I'm using right now looks something like this:

# withing some test
debugger unless some_variable.nil?
expect(some_variable).to be_nil

However, this approach is cumbersome, because I first wait for a test to fail, then add the debugger line, fix the issue and then have to remove the debugger line, whereas I want it work more like gdb which has the ability to kick in when an exception is hit, without requiring to pepper your code base with debugger statements.

Edit: I've tried Plymouth. It hasn't worked reliably enough for me. Also the development history seems to indicate that it isn't a very well supported gem, so I'd rather not rely on it.

Update: I tried out pry-rescue and find it to be neat. However, I use zeus a lot and was wondering if there's a way to make it work with pry-rescue.

Posting answered 30/4, 2013 at 0:26 Comment(0)
M
35

Use pry-rescue, it's the spiritual successor to plymouth:

From the Readme:

If you're using RSpec or respec, you can open a pry session on every test failure using rescue rspec or rescue respec:

$ rescue rspec
From: /home/conrad/0/ruby/pry-rescue/examples/example_spec.rb @ line 9 :

     6:
     7: describe "Float" do
     8:   it "should be able to add" do
 =>  9:     (0.1 + 0.2).should == 0.3
    10:   end
    11: end

RSpec::Expectations::ExpectationNotMetError: expected: 0.3
     got: 0.30000000000000004 (using ==)
[1] pry(main)>
Milline answered 30/4, 2013 at 15:7 Comment(3)
Just tried it with the newest zeus and pry-rescue version, doesn't workSenary
I'm able to get control upon test case failure but I don't have access to associations, example customer.addresses returns a [] while there are addresses.Automatism
This works, however when it's rescued it's too late as you've lost the stack frame that contains the useful test state. Any suggestions to get around this?Tanta
E
11

You won't get access to local variables (easily) without debugger being in scope of the block, however RSpec provides you with around hooks which let's you do this:

config.around(:each) do |example|
  result = example.run
  debugger if result.is_a?(Exception)
  puts "Debugging enabled"
end

You then have access to @ivars and subject / let(:var) contents at this point.

Engelbert answered 30/4, 2013 at 6:59 Comment(1)
Interesting. This would not catch test failures though, would it?Posting
E
10

I like @jon-rowe's solution (no additional gems needed) with a slight edit: I really don't care about other errors as much as RSpec::Expectations::ExpectationNotMetError.

config.around(:each) do |example|
  example.run.tap do |result|
    debugger if result.is_a?(RSpec::Expectations::ExpectationNotMetError)
  end
end
Elviaelvie answered 14/10, 2015 at 13:51 Comment(2)
This no longer works because the RSpec hooks API is a little different now, (i.e. run does not return the result)Tanta
@Tanta I just checked on RSpec 3.13.0 on Ruby 3.3 and it appears to still work for a basic case. (No RSpec Rails, just rspec(core/expectations/mocks) gist.github.com/stringsn88keys/afeae96ce4a3b712c23ad31df60d47bbElviaelvie
P
5

From the Rspec documentation:

RSpec tries to provide useful failure messages, but for cases in which you want more specific information, you can define your own message right in the example.This works for any matcher other than the operator matchers.

What I do is to invoke pry in that message. See the example:

describe "failing" do
  context "test" do
    it "should start pry" do
      a = 3
      b = 1
      expect(a).to be == b, "#{require 'pry';binding.pry}"
    end
  end
end

Happy debugging!

Pl answered 11/2, 2021 at 8:7 Comment(3)
If you're not using a repl, or don't have one available, this alternative message is the perfect place to (temporarily) drop an inspect or some other bit of debugging information.Crossquestion
Slick Rick! Note, if you're already requiring pry, I was able to use just expect(a).to be == b, binding.pry.Vitta
I tried adding binding.pry like so and it binds every time rather than on failure? ``` expect(a).to be == b, binding.pry ```Tanta
C
1

You need to catch the ExpectationNotMatched exception while it's being constructed. Include the following code in your helpers somewhere and RSpec will stop when the exception is being constructed. This will be several levels deep inside the matchers, so in the debugger, say "where" then "up 5"or "up 6" and you'll be inside the instance_exec of your block. The debugger doesn't show the code correctly in the version I'm using, but you can "up" one more time and get to code running in the same context where your test is evaluated, so you can inspect the instance variables (but not local variables, it seems).

require 'debugger'
require 'rspec'

Debugger.start
class RSpec::Expectations::ExpectationNotMetError
  alias_method :firstaid_initialize, :initialize

  def initialize *args, &b
    send(:firstaid_initialize, *args, &b)
    puts "Stopped due to #{self.class}: #{message} at "+caller*"\n\t"
    debugger
    true # Exception thrown
  end
end

describe "RSpec" do
  it "should load use exceptions on should failure" do
    @foo = :bar    # An instance variable I can examine
    1.should == 2
  end
end
Constrict answered 30/4, 2013 at 1:56 Comment(1)
This looks very promising. I'll try it out. Thanks, Clifford!Posting
T
0

You can use plymouth gem https://github.com/banister/plymouth for that. It is using pry though, a (better) alternative to irb.

HTH

Trojan answered 30/4, 2013 at 0:37 Comment(1)
I've tried Plymouth. It hasn't worked reliably enough for me. Also the development history seems to indicate that it isn't a very well supported gem, so I'd rather not rely on it.Posting
A
-1

You can try hammertime. It will stop and prompt to take you into an interactive debugging session whenever an exception is raised.

Adagietto answered 30/4, 2013 at 3:55 Comment(1)
pry-rescue (a pry plugin) is far superior to hammertime, it drops you in the actual context of the exception and lets you walk the stackMilline

© 2022 - 2024 — McMap. All rights reserved.