How can I test :inclusion validation in Rails using RSpec
Asked Answered
P

4

47

I have the following validation in my ActiveRecord.

validates :active, :inclusion => {:in => ['Y', 'N']}

I am using the following to test my model validations.

should_not allow_value('A').for(:active)
should allow_value('Y').for(:active)
should allow_value('N').for(:active)

Is there a cleaner and more through way of testing this? I am currently using RSpec2 and shoulda matchers.

EDIT

After some looking around I only found, this probably an 'ok' way of testing this, shoulda does not provide anything for this and anyone who requires it can write their own custom matcher for it.(And probably contribute it back to the project). Some links to discussions that might be intresting:

  • Links which indicate to the above . Link 1 , Link 2

  • should_ensure_value_in_range This one comes close to what can be used, but only accepts ranges and not a list of values. Custom matcher can be based on this.

Prohibit answered 14/9, 2011 at 11:34 Comment(0)
C
80

Use shoulda_matchers

In recent versions of shoulda-matchers (at least as of v2.7.0), you can do:

expect(subject).to validate_inclusion_of(:active).in_array(%w[Y N])

This tests that the array of acceptable values in the validation exactly matches this spec.

In earlier versions, >= v1.4 , shoulda_matchers supports this syntax:

it {should ensure_inclusion_of(:active).in_array(%w[Y N]) }
Chirrup answered 6/8, 2012 at 15:58 Comment(8)
To check that it's disallowing other values, you can do something like: it { should_not allow_value('?').for(:active) } -- like you said, you can't check all possible values, but doing this in addition to checking all allowed values seems like reasonable coverage.Boote
The shoulda_matcher you referred to does work as you originally stated, i.e. it disallows values not in the provided array. See documentation. For good measure, I tested this in a Rails app and it works correctly.Stoneham
@LarsLevie - Thanks for the comment. Looks like they changed the validation to check disallows_value_outside_of_array?. See the old github.com/thoughtbot/shoulda-matchers/blob/v1.2.0/lib/shoulda/… vs now github.com/thoughtbot/shoulda-matchers/blob/…Chirrup
Why is the documentation not clear on this? It is still showing : validates_inclusion_onAcadia
As of at least shoulda-matchers v2.7.0, this syntax is deprecated. The new preferred syntax using RSpec 3.0 expect syntax would be: expect(subject).to validate_inclusion_of(:active).in_array(%w[Y N])Dyslalia
@NathanWallace That's the new Rspec 3 syntax, but did shoulda-matchers actually change anything to support it and deprecate the old? I thought it was just a change in the way Rspec uses matchers, not a requirement for matchers to be rewritten.Chirrup
The change in the matcher is that it's no longer ensure_inclusion_of, now it's validate_inclusion_ofDyslalia
FYI, when running the test suite with this for a boolean value I got the following warning: Warning from shoulda-matchers: You are using "validate_inclusion_of" to assert that a boolean column allows boolean values and disallows non-boolean ones. Be aware that it is not possible to fully test this, as boolean columns will automatically convert non-boolean values to boolean ones. Hence, you should consider removing this test.Rimskykorsakov
B
26

If you have more elements to test than a boolean Y/N then you could also try.

it "should allow valid values" do
  %w(item1 item2 item3 item4).each do |v|
    should allow_value(v).for(:field)
  end
end
it { should_not allow_value("other").for(:role) }

You can also replace the %w() with a constant you have defined in your model so that it tests that only the constant values are allowed.

CONSTANT = %w[item1 item2 item3 item4]
validates :field, :inclusion => CONSTANT

Then the test:

it "should allow valid values" do
  Model::CONSTANT.each do |v|
    should allow_value(v).for(:field)
  end
end
Bushnell answered 18/10, 2011 at 3:59 Comment(0)
P
2

I found one custom shoulda matcher (in one of the projects I was working on) which attempts to coming close to test something like this:

Examples:

it { should validate_inclusion_check_constraint_on :status, :allowed_values => %w(Open Resolved Closed) }
it { should validate_inclusion_check_constraint_on :age, :allowed_values => 0..100 }

The matcher tries to ensure that there is a DB constraint which blows up when it tries to save it.I will attempt to give the essence of the idea. The matches? implementation does something like:

  begin
    @allowed_values.each do |value|
      @subject.send("#{@attribute}=", value)
      @subject.save(:validate => false)
    end
  rescue ::ActiveRecord::StatementInvalid => e
    # Returns false if the exception message contains a string matching the error throw by SQL db
  end

I guess if we slightly change the above to say @subject.save and let Rails validation blow up, we can return false when the exception string contains something which close matches the real exception error message.

I know this is far from perfect to contributed back to the project, but I guess might not be a bad idea to add into your project as a custom matcher if you really want to test a lot of the :inclusion validation.

Prohibit answered 22/9, 2011 at 3:4 Comment(1)
I will accept my own answer here since I can't seem to find anything else, but please feel free to criticize how the above solution can fail, or if its even a bad idea to bother doing this.Prohibit
M
0

In shoulda-matchers >= 5 you should be able to use :validate_inclusion_of as

it { should validate_inclusion_of(:active).in_array(%w[Y N]) }
Mercurochrome answered 19/3, 2023 at 13:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.