views:

55

answers:

4

Is there any way to simulate the absence of a gem for certain unit tests, short of actually uninstalling and then reinstalling the gem during testing?

I am writing a command line utility, and want to make sure that my tests cover cases where a user may not have all of the gems that I support. For instance, I am using fsevents — a Leopard-specific package for monitoring filesystem events — that will never be present on other systems, as well as a growl gem that's purely optional.

+1  A: 

You can test the existence of the constant.

if !defined?(Growl)
  ...
end

In your tests, you can temporary rename/disable/remove the constant to simulate the missing library.

Simone Carletti
+1  A: 

I would try to reset the gem path, something like:

require 'rubygems' 
Gem.clear_paths 
ENV['GEM_HOME'] = "" 
ENV['GEM_PATH'] = "" 

I would also save it and restore it afterwards.

philippe
Nuking the paths does indeed prevent the gem loading, but I'm evidently not recovering properly: http://pastie.org/932093Any hints?It certainly seems that you're on the right track:Gem::clear_paths: “Reset the +dir+ and +path+ values. The next time +dir+ or +path+ is requested, the values will be calculated from scratch. *This is mainly used by the unit tests to provide test isolation.*”
Kyle Mitchell
+1  A: 

I used to use a method I wrote called with_constant_unavailable. I've forgotten the exact details, but I think it was something like this:

def with_constant_unavailable(constant_name, &block)
  match_data = constant_name.to_s.match(/(?:(.+)::)?(.+)/)
  if match_data[2]
    owning_module, constant_name = match_data[1].constantize, match_data[2]
  else
    owning_module, constant_name = Object, constant_name.to_s
  end
  original_constant = owning_module.send :remove_const, constant_name
  begin
    yield
  ensure
    owning_module.const_set constant_name, original_constant
  end
end

Then you can run a test like so:

def test_uses_foo_when_bar_unavailable
  with_constant_unavailable(Bar) do
    assert baz.quux == Foo
  end
end
James A. Rosen
A: 

Once you've required something I think it's pretty difficult to undo that. What you could do, though, is have a separate set of tests that always run in a separate Ruby interpreter instance, and invoke them as a different rake task. So you might have something like this:

Rake::TestTask.new(:no_growl_test) do |t|
  t.libs << 'lib'
  t.pattern = 'test/compatibility/no_growl.rb'
end

The idea being that this would run in a new, "clean" environment in which you could never load Growl. Some of the other users have suggested ways that you could make Rubygems not find the gem in question, and this might even be possible in the Gem API.

Alternatively, using JRuby it's not too difficult to create multiple disjoint run environments, but that's probably overkill for what you're doing.

Calum