Michael Righi
Michael Righi

Reputation: 1353

How do I prevent RSpec from concealing missing dependencies?

I have a Ruby program that fails at runtime, but works when I test it with RSpec. I know the cause of the bug and how to fix it (see below), but I can't figure out how to build a failing RSpec test which proves the existence of the bug.

Imagine the following Ruby:

foobar.rb

class Foobar
  attr_reader :fruit
  def initialize
    @fruit = Set.new ["Apple", "Banana", "Kiwi"]
  end
end

The above code uses a Set, but it fails to "require 'set'". This causes it to fail at runtime:

$ irb
> require './foobar.rb'
> f = Foobar.new
NameError: uninitialized constant Foobar::Set

Before fixing the oversight, I wanted to build a simple RSpec test that proves the bug. My test looks like this:

foobar_spec.rb

require 'rspec'
require './foobar.rb'

describe Foobar do
  it "can be initialized" do
    expect { Foobar.new }.to_not raise_error
  end
end

Running the test, I was surprised to see that it passes:

$ rspec foobar_spec.rb
.

Finished in 0.00198 seconds
1 example, 0 failures

After a little digging, I learned that RSpec loads Set for itself. This has the consequence of making Set available to the code it tests, and in my case concealing a bug.

I had the idea of "unloading/unrequiring" Set in my test. The closest I came was this code:

Object.send(:remove_const, :Set)

That indeed causes the test to fail, but unfortunately it also prevents Set from being loaded again by a future 'require', meaning it continued to fail even after I added require 'set' inside foobar.rb.

Is there a better way to unload gems at runtime? If not, what can I do to make this test fail as it should?

Upvotes: 2

Views: 152

Answers (1)

zetetic
zetetic

Reputation: 47548

require 'rspec'

describe 'foobar.rb' do
  it "can instantiate Foobar" do
    `ruby -e 'Foobar.new' -r./foobar.rb`
    $?.exitstatus.should == 0
  end
end

works for the one case you mentioned. That said, I wouldn't recommend this approach. To cover all the cases where a class is referenced, you'd need to run all your specs this way, since the class reference could appear anywhere in your code.

Upvotes: 3

Related Questions