Steve Folly Steve Folly - 1 month ago 9
Ruby Question

Keep DRY, but want to repeat for different reasons

So in my evolving rspecs for my RoR model, I ended up with two tests exactly the same:

it 'is valid when x is zero' do
foo = build(:foo, x: 0, y: 10)
expect(foo.valid?).to be_truthy
end
it 'is valid when y is ten' do
foo = build(:foo, x: 0, y: 10)
expect(foo.valid?).to be_truthy
end


This came about because I wrote the spec for validating x first, then added the spec for y after.

Obviously, time to refactor. I could delete one of the specs because they're duplicates: keep it DRY.

Now, the internals of each spec may be exactly the same, but the
it
descriptions are different. I don't want to lose the information contained there.

My questions is - is it acceptable in this case to keep the duplicates specs intact, or should I 'merge' them and reword the
it
description? Perhaps:

it 'is valid when x is zero and y is ten' do
foo = build(:foo, x: 0, y: 10)
expect(foo.valid?).to be_truthy
end


But to my mind, I now have one spec that is testing two things (the two validate clauses in the Foo model). That's not good, either. There's a smell lurking.

Is there another approach I've missed?

max max
Answer

I would worry less about DRY and more about writing specs that actually cover the behavior you intend to.

it 'is valid when x is zero' do
  foo = build(:foo, x: 0)
  expect(foo.valid?).to be_truthy
end

This example actually does not cover your validation at all! If you comment out the validation in your model it will still pass.

Some tips when testing model validations:

  • Avoid using factories. Just initialize with .new and the attribute under test.
  • Test for both invalid and valid input.
  • Describe the behavior of the validation - not which values are acceptable.
  • Test each validation in isolation - your integration and functional specs will usually cover the validations as a whole.

RSpec.describe Foo do
  describe "validations" do
    describe 'x' do
      it "validates that x is a number between 1 and 10" do
        expect(Foo.new(500).valid?.errors[:x]).to include "must be less than or equal to 10".
        expect(Foo.new(10).valid?.errors).to_not have_key :x
      end
    end

    describe 'y' do
      it "validates that y is a number that is less than 15" do
        expect(Foo.new(500).valid?.errors[:y]).to include "must be less than 15".
        expect(Foo.new(10).valid?.errors).to_not have_key :y
      end
    end
  end
end
Comments