Question

In my Rails 3 application, I have a RSpec spec that checks behavior of a given field (role in the User model) to guarantee that the value is within a list of valid values.

Now I am going to have the exact same spec for another field, in another model with another set of valid values. I would like to extract the common code instead of merely copying and pasting it, changing the variables.

I am wondering if this would be the case to use a shared example or other RSpec reuse technique.

Here's the relevant RSpec code:

describe "validation" do  
  describe "#role" do
    context "with a valid role value" do
      it "is valid" do
        User::ROLES.each do |role|
          build(:user, :role => role).should be_valid
        end
      end
    end

    context "with an empty role" do
      subject { build(:user, :role => nil) }

      it "is invalid" do
        subject.should_not be_valid
      end

      it "adds an error message for the role" do
        subject.save.should be_false
        subject.errors.messages[:role].first.should == "can't be blank"
      end
    end

    context "with an invalid role value" do
      subject { build(:user, :role => 'unknown') }

      it "is invalid" do
        subject.should_not be_valid
      end

      it "adds an error message for the role" do
        subject.save.should be_false
        subject.errors.messages[:role].first.should =~ /unknown isn't a valid role/
      end
    end
  end
end

What would be the best case to reuse this code, but extracting role (the field being verified) and User::ROLES (the collection of valid values) into parameters being passed to this code?

Was it helpful?

Solution

I think this is a perfectly reasonable use case for shared examples. e.g. something like this:

shared_examples_for "attribute in collection" do |attr_name, valid_values|

  context "with a valid role value" do
    it "is valid" do
      valid_values.each do |role|
        build(:user, attr_name => role).should be_valid
      end
    end
  end

  context "with an empty #{attr_name}" do
    subject { build(:user, attr_name => nil) }

    it "is invalid" do
      subject.should_not be_valid
    end

    it "adds an error message for the #{attr_name}" do
      subject.save.should be_false
      subject.errors.messages[attr_name].first.should == "can't be blank"
    end
  end

  context "with an invalid #{attr_name} value" do
    subject { build(:user, attr_name => 'unknown') }

    it "is invalid" do
      subject.should_not be_valid
    end

    it "adds an error message for the #{attr_name}" do
      subject.save.should be_false
      subject.errors.messages[attr_name].first.should =~ /unknown isn't a valid #{attr_name}/
    end
  end
end

Then you can call it in your specs like this:

describe "validation" do  
  describe "#role" do
    behaves_like "attribute in collection", :role, User::ROLES
  end
end

Haven't tested this but I think it should work.

OTHER TIPS

You can DRY your spec with shared_examples technic this way:

  shared_examples "no role" do
    it "is invalid" do
      subject.should_not be_valid
    end
  end

  context "with an empty role" do
    subject { Factory.build(:user, :name => nil) }
    it_behaves_like "no role"
  end

  context "with an invalid role value" do
    subject { Factory.build(:user, :name => '') }
    it_behaves_like "no role"
  end

But what about your idea to DRY few specs..I think it's too much. I'm convince that spec has to be readable firstly and only then DRY'ing. If you DRY few specs, it will be probably a headache for future reading/refactoring/changing this code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top