45

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.

jake
  • 2,371
  • 1
  • 20
  • 31

4 Answers4

78

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]) }
Nathan Long
  • 122,748
  • 97
  • 336
  • 451
  • 1
    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. – bjnord Apr 10 '13 at 18:21
  • The shoulda_matcher you referred to **does** work as you originally stated, i.e. it disallows values not in the provided array. [See documentation](https://github.com/thoughtbot/shoulda-matchers#ensure_inclusion_of). For good measure, I tested this in a Rails app and it works correctly. – Lars Levie Dec 17 '13 at 22:13
  • 1
    @LarsLevie - Thanks for the comment. Looks like they changed the validation to check `disallows_value_outside_of_array?`. See the old https://github.com/thoughtbot/shoulda-matchers/blob/v1.2.0/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb#L86 vs now https://github.com/thoughtbot/shoulda-matchers/blob/15abdf066732828034efea751c2937aa81d080fe/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb#L90 – Nathan Long Dec 18 '13 at 13:25
  • Why is the documentation not clear on this? It is still showing : validates_inclusion_on – Sankalp Singha Jul 26 '14 at 10:19
  • 1
    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])` – Nathan Wallace Nov 25 '14 at 16:14
  • @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. – Nathan Long Nov 25 '14 at 17:00
  • 1
    The change in the matcher is that it's no longer `ensure_inclusion_of`, now it's `validate_inclusion_of` – Nathan Wallace Nov 25 '14 at 18:21
  • 2
    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.` – Joshua Pinter Oct 14 '18 at 17:36
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
nmott
  • 9,454
  • 3
  • 45
  • 34
1

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.

jake
  • 2,371
  • 1
  • 20
  • 31
  • 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. – jake Sep 22 '11 at 03:06
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]) }
mrateb
  • 2,317
  • 5
  • 27
  • 56