开发者

Rspec false positive because failure exception is rescued in code being tested

I have an rspec test that I expect to fail, but it is passing because the code that it is testing rescues the exception that rspec raises. Here's an example of the situation:

class Thing do

  def self.method_being_tested( object )
    # ... do some stuff

    begin
      object.save!
 开发者_Go百科   rescue Exception => e
      # Swallow the exception and log it
    end
  end

end

In the rspec file:

describe "method_being_tested" do
  it "should not call 'save!' on the object passed in" do
    # ... set up the test conditions

    mock_object.should_not_receive( :save! )
    Thing.method_being_tested( mock_object )
  end
end

I knew that the execution was reaching the "object.save!" line of the method being tested, and the test should therefore be failing, but the test passes. Using the debugger in the rescue block, I find the following:

(rdb:1) p e # print the exception object "e"
#<RSpec::Mocks::MockExpectationError: (Mock "TestObject_1001").save!
    expected: 0 times
    received: 1 time>

So basically the test is failing but, but the failure is being suppressed by the very code it is trying to test. I cannot figure out a viable way to stop this code from swallowing Rspec exceptions without somehow compromising the code. I don't want the code to explicitly check if the exception is an Rspec exception, because that is bad design (tests should be written for code, code should never be written for tests). But I also can't check that the exception is any particular type that I DO want it to catch, because I want it to catch ANYTHING that could be raised in a normal production environment.

Someone must have had this problem before me! Please help me find a solution.


Assuming the code is correct as-is:

describe "method_being_tested" do
  it "should not call 'save!' on the object passed in" do
    # ... set up the test conditions
    calls = 0
    mock_object.stub(:save!) { calls += 1 }
    expect {Thing.method_being_tested(mock_object)}.to_not change{calls}
  end
end

If there's no need to catch absolutely all exceptions including SystemExit, NoMemoryError, SignalException etc (input from @vito-botta):

begin
  object.save!
rescue StandardError => e
  # Swallow "normal" exceptions and log it
end

StandardError is the default exception level caught by rescue.


from rspec-mock:

module RSpec
  module Mocks
    class MockExpectationError < Exception
    end

    class AmbiguousReturnError < StandardError
    end
  end
end

Do you really need to catch Exception? Could you catch StandardError instead?

Catching all exceptions is generally a bad thing.


I would refactor it like so:

class Thing do

  def self.method_being_tested!( object )

    # ... do some stuff

    return object.save
  end

end

If you want to ignore the exception thrown by save! there is no point in calling save! in the first place. You just call save and inform the calling code accordingly.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜