Flambino
Flambino

Reputation: 18773

Unit testing with and without requiring ActiveSupport

I've extracted a single class from a Rails app into a gem. It's very, very simple, but of course I'd like to fully test it (I'm using rspec).

The class does some simple date-calculation. It's not dependent on Rails, but since it started out in a Rails app, and is still used there, it uses ActiveSupport's time zone-aware methods when it can. But, if ActiveSupport isn't available, it should use the std-lib Date methods.

Specifically, it only does this in one single place: Defaulting an optional argument to "today's date":

arg ||= if Date.respond_to?(:current)
  Date.current # use ActiveSupport's time zone-aware mixin if possible
else
  Date.today   # stdlib fallback
end

Question is: How do I properly test this? If I require ActiveSupport in my spec_helper.rb, it'll obviously always use that. If I don't require it anywhere, it'll never use it. And if I require it for a single example group, rspec's random execution order makes the testing unpredictable, as I don't know when AS will be required.

I can require maybe it in a before(:all) in a nested group, as nested groups are (I believe) processed highest to deepest. But that seems terribly inelegant.

I could also split the specs into two files, and run them separately, but again, that seems unnecessary.

I could also disable rspec's random ordering, but that's sort of going against the grain. I'd rather have it as randomized as possible.

Any ideas?

Upvotes: 3

Views: 230

Answers (2)

David Miani
David Miani

Reputation: 14668

Another solution is to mock the current and today methods, and use those for testing. Eg:

# you won't need these two lines, just there to make script work standalone
require 'rspec'
require 'rspec/mocks/standalone'

def test_method(arg = nil)
    arg ||= if Date.respond_to?(:current)
      Date.current # use ActiveSupport's time zone-aware mixin if possible
    else
      Date.today   # stdlib fallback
    end
    arg
end

describe "test_method" do
    let(:test_date) { Date.new(2001, 2, 3) }
    it "returns arg unchanged if not nil" do
        test_method(34).should == 34
    end

    context "without Date.current available" do
        before(:all) do
            Date.stub(:today) { test_date }
        end
        it "returns Date.today when arg isn't present" do
            test_method.should == test_date
        end
    end

    context "with Date.current available" do
        before(:all) do
            Date.stub(:current) { test_date }
        end
        it "returns Date.current when arg isn't present" do
            test_method.should == test_date
        end
    end
end

Running with rspec test.rb results in the tests passing.

Also, the stubs are present only in each context, so it doesn't matter what order the specs are run in.

Upvotes: 2

Wally Altman
Wally Altman

Reputation: 3545

This is more than a little perverse, but it should work. Include ActiveSupport, and then:

context "without ActiveSupport's Date.current" do
  before(:each) do
    class Date
      class << self
        alias_method :current_backup, :current
        undef_method :current
      end
    end
  end

  # your test

  after(:each) do
    class Date
      class << self
        alias_method :current, :current_backup
      end
    end
  end
end

I can't really recommend this; I would prefer to split out this one spec and run it separately as you suggested.

Upvotes: 0

Related Questions