module Shoulda::Matchers::ActiveModel
This mixin provides matchers that are used to test behavior, such as validations, that you've added to your ActiveModel (or ActiveRecord) objects.
### Testing conditional validations
If your model defines a validation conditionally – meaning that the validation is declared with an `:if` or `:unless` option – how do you test it? You might expect the validation matchers here to have corresponding `if` or `unless` qualifiers, but this isn't what you use. Instead, before using the matcher in question, you place the record you're testing in a state such that the validation you're also testing will be run. A common way to do this is to make a new `context` and override the subject to populate the record accordingly. You'll also want to make sure to test that the validation is not run when the conditional fails.
Here's an example to illustrate what we mean:
class User include ActiveModel::Model attr_accessor :role, :admin validates_presence_of :role, if: :admin end # RSpec describe User do context "when an admin" do subject { User.new(admin: true) } it { should validate_presence_of(:role) } end context "when not an admin" do subject { User.new(admin: false) } it { should_not validate_presence_of(:role) } end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase context "when an admin" do subject { User.new(admin: true) } should validate_presence_of(:role) end context "when not an admin" do subject { User.new(admin: false) } should_not validate_presence_of(:role) end end
Attributes
Public Instance Methods
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 388 def _after_setting_value(&callback) @after_setting_value_callback = callback end
The `allow_mass_assignment_of` matcher tests usage of Rails 3's `attr_accessible` and `attr_protected` macros, asserting that an attribute in your model is contained in either the whitelist or blacklist and thus can or cannot be set via mass assignment.
class Post include ActiveModel::Model include ActiveModel::MassAssignmentSecurity attr_accessor :title attr_accessible :title end class User include ActiveModel::Model include ActiveModel::MassAssignmentSecurity attr_accessor :encrypted_password attr_protected :encrypted_password end # RSpec describe Post do it { should allow_mass_assignment_of(:title) } end describe User do it { should_not allow_mass_assignment_of(:encrypted_password) } end # Minitest (Shoulda) class PostTest < ActiveSupport::TestCase should allow_mass_assignment_of(:title) end class UserTest < ActiveSupport::TestCase should_not allow_mass_assignment_of(:encrypted_password) end
#### Optional qualifiers
##### as
Use `as` if your mass-assignment rules apply only under a certain role *(Rails >= 3.1 only)*.
class Post include ActiveModel::Model include ActiveModel::MassAssignmentSecurity attr_accessor :title attr_accessible :title, as: :admin end # RSpec describe Post do it { should allow_mass_assignment_of(:title).as(:admin) } end # Minitest (Shoulda) class PostTest < ActiveSupport::TestCase should allow_mass_assignment_of(:title).as(:admin) end
@return [AllowMassAssignmentOfMatcher]
# File lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb, line 70 def allow_mass_assignment_of(value) AllowMassAssignmentOfMatcher.new(value) end
The `allow_value` matcher (or its alias, `allow_values`) is used to ensure that an attribute is valid or invalid if set to one or more values.
Take this model for example:
class UserProfile include ActiveModel::Model attr_accessor :website_url validates_format_of :website_url, with: URI.regexp end
You can use `allow_value` to test one value at a time:
# RSpec describe UserProfile do it { should allow_value('http://foo.com').for(:website_url) } it { should allow_value('http://bar.com').for(:website_url) } end # Minitest (Shoulda) class UserProfileTest < ActiveSupport::TestCase should allow_value('http://foo.com').for(:website_url) should allow_value('http://bar.com').for(:website_url) end
You can also test multiple values in one go, if you like. In the positive sense, this makes an assertion that none of the values cause the record to be invalid. In the negative sense, this makes an assertion that none of the values cause the record to be valid:
# RSpec describe UserProfile do it do should allow_values('http://foo.com', 'http://bar.com'). for(:website_url) end it do should_not allow_values('http://foo.com', 'buz'). for(:website_url) end end # Minitest (Shoulda) class UserProfileTest < ActiveSupport::TestCase should allow_values('http://foo.com', 'http://bar.com/baz'). for(:website_url) should_not allow_values('http://foo.com', 'buz'). for(:website_url) end
#### Caveats
When using `allow_value` or any matchers that depend on it, you may encounter an AttributeChangedValueError. This exception is raised if the matcher, in attempting to set a value on the attribute, detects that the value set is different from the value that the attribute returns upon reading it back.
This usually happens if the writer method (`foo=`, `bar=`, etc.) for that attribute has custom logic to ignore certain incoming values or change them in any way. Here are three examples we've seen:
-
You're attempting to assert that an attribute should not allow nil, yet the attribute's writer method contains a conditional to do nothing if the attribute is set to nil:
class Foo include ActiveModel::Model attr_reader :bar def bar=(value) return if value.nil? @bar = value end end describe Foo do it do foo = Foo.new foo.bar = "baz" # This will raise an AttributeChangedValueError since `foo.bar` is now "123" expect(foo).not_to allow_value(nil).for(:bar) end end
-
You're attempting to assert that a numeric attribute should not allow a string that contains non-numeric characters, yet the writer method for that attribute strips out non-numeric characters:
class Foo include ActiveModel::Model attr_reader :bar def bar=(value) @bar = value.gsub(/\D+/, '') end end describe Foo do it do foo = Foo.new # This will raise an AttributeChangedValueError since `foo.bar` is now "123" expect(foo).not_to allow_value("abc123").for(:bar) end end
-
You're passing a value to `allow_value` that the model typecasts into another value:
describe Foo do # Assume that `attr` is a string # This will raise an AttributeChangedValueError since `attr` typecasts `[]` to `"[]"` it { should_not allow_value([]).for(:attr) } end
Fortunately, if you understand why this is happening, and wish to get around this exception, it is possible to do so. You can use the `ignoring_interference_by_writer` qualifier like so:
it do should_not allow_value([]). for(:attr). ignoring_interference_by_writer end
Please note, however, that this qualifier won't magically cause your test to pass. It may just so happen that the final value that ends up being set causes the model to fail validation. In that case, you'll have to figure out what to do. You may need to write your own test, or perhaps even remove your test altogether.
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class UserProfile include ActiveModel::Model attr_accessor :birthday_as_string validates_format_of :birthday_as_string, with: /^(\d+)-(\d+)-(\d+)$/, on: :create end # RSpec describe UserProfile do it do should allow_value('2013-01-01'). for(:birthday_as_string). on(:create) end end # Minitest (Shoulda) class UserProfileTest < ActiveSupport::TestCase should allow_value('2013-01-01'). for(:birthday_as_string). on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class UserProfile include ActiveModel::Model attr_accessor :state validates_format_of :state, with: /^(open|closed)$/, message: 'State must be open or closed' end # RSpec describe UserProfile do it do should allow_value('open', 'closed'). for(:state). with_message('State must be open or closed') end end # Minitest (Shoulda) class UserProfileTest < ActiveSupport::TestCase should allow_value('open', 'closed'). for(:state). with_message('State must be open or closed') end
Use `with_message` with a regexp to perform a partial match:
class UserProfile include ActiveModel::Model attr_accessor :state validates_format_of :state, with: /^(open|closed)$/, message: 'State must be open or closed' end # RSpec describe UserProfile do it do should allow_value('open', 'closed'). for(:state). with_message(/open or closed/) end end # Minitest (Shoulda) class UserProfileTest < ActiveSupport::TestCase should allow_value('open', 'closed'). for(:state). with_message(/open or closed/) end
Use `with_message` with the `:against` option if the attribute the validation message is stored under is different from the attribute being validated:
class UserProfile include ActiveModel::Model attr_accessor :sports_team validate :sports_team_must_be_valid private def sports_team_must_be_valid if sports_team !~ /^(Broncos|Titans)$/i self.errors.add :chosen_sports_team, 'Must be either a Broncos fan or a Titans fan' end end end # RSpec describe UserProfile do it do should allow_value('Broncos', 'Titans'). for(:sports_team). with_message('Must be either a Broncos or Titans fan', against: :chosen_sports_team ) end end # Minitest (Shoulda) class UserProfileTest < ActiveSupport::TestCase should allow_value('Broncos', 'Titans'). for(:sports_team). with_message('Must be either a Broncos or Titans fan', against: :chosen_sports_team ) end
##### ignoring_interference_by_writer
Use `ignoring_interference_by_writer` to bypass an AttributeChangedValueError that you have encountered. Please read the Caveats section above for more information.
class Address < ActiveRecord::Base # Address has a zip_code field which is a string end # RSpec describe Address do it do should_not allow_value([]). for(:zip_code). ignoring_interference_by_writer end end # Minitest (Shoulda) class AddressTest < ActiveSupport::TestCase should_not allow_value([]). for(:zip_code). ignoring_interference_by_writer end
@return [AllowValueMatcher]
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 296 def allow_value(*values) if values.empty? raise ArgumentError, 'need at least one argument' else AllowValueMatcher.new(*values) end end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 500 def description ValidationMatcher::BuildDescription.call(self, simple_description) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 398 def does_not_match?(instance) @instance = instance @result = run(:first_passing) @result.nil? end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 365 def expected_message if options.key?(:expected_message) if Symbol === options[:expected_message] default_expected_message else options[:expected_message] end end end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 375 def expects_custom_validation_message? @expects_custom_validation_message end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 384 def expects_strict? @expects_strict end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 404 def failure_message attribute_setter = result.attribute_setter if result.attribute_setter.unsuccessfully_checked? message = attribute_setter.failure_message else validator = result.validator message = failure_message_preface.call message << ' valid, but it was invalid instead,' if validator.captured_validation_exception? message << ' raising a validation exception with the message ' message << validator.validation_exception_message.inspect message << '.' else message << " producing these validation errors:\n\n" message << validator.all_formatted_validation_error_messages end end if include_attribute_changed_value_message? message << "\n\n" + attribute_changed_value_message.call end Shoulda::Matchers.word_wrap(message) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 431 def failure_message_when_negated attribute_setter = result.attribute_setter if attribute_setter.unsuccessfully_checked? message = attribute_setter.failure_message else validator = result.validator message = failure_message_preface.call + ' invalid' if validator.type_of_message_matched? if validator.has_messages? message << ' and to' if validator.captured_validation_exception? message << ' raise a validation exception with message' else message << ' produce' if expected_message.is_a?(Regexp) message << ' a' else message << ' the' end message << ' validation error' end if expected_message.is_a?(Regexp) message << ' matching ' message << Shoulda::Matchers::Util.inspect_value( expected_message ) else message << " #{expected_message.inspect}" end unless validator.captured_validation_exception? message << " on :#{attribute_to_check_message_against}" end message << '. The record was indeed invalid, but' if validator.captured_validation_exception? message << ' the exception message was ' message << validator.validation_exception_message.inspect message << ' instead.' else message << " it produced these validation errors instead:\n\n" message << validator.all_formatted_validation_error_messages end else message << ', but it was valid instead.' end elsif validator.captured_validation_exception? message << ' and to produce validation errors, but the record' message << ' raised a validation exception instead.' else message << ' and to raise a validation exception, but the record' message << ' produced validation errors instead.' end end if include_attribute_changed_value_message? message << "\n\n" + attribute_changed_value_message.call end Shoulda::Matchers.word_wrap(message) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 337 def for(attribute_name) @attribute_to_set = attribute_name @attribute_to_check_message_against = attribute_name self end
The `have_secure_password` matcher tests usage of the `has_secure_password` macro.
#### Example
class User include ActiveModel::Model include ActiveModel::SecurePassword attr_accessor :password has_secure_password end # RSpec describe User do it { should have_secure_password } end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should have_secure_password end
@return [HaveSecurePasswordMatcher]
# File lib/shoulda/matchers/active_model/have_secure_password_matcher.rb, line 29 def have_secure_password HaveSecurePasswordMatcher.new end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 512 def last_attribute_setter_used result.attribute_setter end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 516 def last_value_set last_attribute_setter_used.value_written end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 392 def matches?(instance) @instance = instance @result = run(:first_failing) @result.nil? end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 508 def model instance.class end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 343 def on(context) if context.present? @context = context end self end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 504 def simple_description "allow :#{attribute_to_set} to be #{inspected_values_to_set}" end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 379 def strict(expects_strict = true) @expects_strict = expects_strict self end
The `validate_absence_of` matcher tests the usage of the `validates_absence_of` validation.
class PowerHungryCountry include ActiveModel::Model attr_accessor :nuclear_weapons validates_absence_of :nuclear_weapons end # RSpec describe PowerHungryCountry do it { should validate_absence_of(:nuclear_weapons) } end # Minitest (Shoulda) class PowerHungryCountryTest < ActiveSupport::TestCase should validate_absence_of(:nuclear_weapons) end
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class PowerHungryCountry include ActiveModel::Model attr_accessor :nuclear_weapons validates_absence_of :nuclear_weapons, on: :create end # RSpec describe PowerHungryCountry do it { should validate_absence_of(:nuclear_weapons).on(:create) } end # Minitest (Shoulda) class PowerHungryCountryTest < ActiveSupport::TestCase should validate_absence_of(:nuclear_weapons).on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class PowerHungryCountry include ActiveModel::Model attr_accessor :nuclear_weapons validates_absence_of :nuclear_weapons, message: "there shall be peace on Earth" end # RSpec describe PowerHungryCountry do it do should validate_absence_of(:nuclear_weapons). with_message("there shall be peace on Earth") end end # Minitest (Shoulda) class PowerHungryCountryTest < ActiveSupport::TestCase should validate_absence_of(:nuclear_weapons). with_message("there shall be peace on Earth") end
@return [ValidateAbsenceOfMatcher}
# File lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb, line 75 def validate_absence_of(attr) ValidateAbsenceOfMatcher.new(attr) end
The `validate_acceptance_of` matcher tests usage of the `validates_acceptance_of` validation.
class Registration include ActiveModel::Model attr_accessor :eula validates_acceptance_of :eula end # RSpec describe Registration do it { should validate_acceptance_of(:eula) } end # Minitest (Shoulda) class RegistrationTest < ActiveSupport::TestCase should validate_acceptance_of(:eula) end
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class Registration include ActiveModel::Model attr_accessor :terms_of_service validates_acceptance_of :terms_of_service, on: :create end # RSpec describe Registration do it do should validate_acceptance_of(:terms_of_service). on(:create) end end # Minitest (Shoulda) class RegistrationTest < ActiveSupport::TestCase should validate_acceptance_of(:terms_of_service).on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class Registration include ActiveModel::Model attr_accessor :terms_of_service validates_acceptance_of :terms_of_service, message: 'You must accept the terms of service' end # RSpec describe Registration do it do should validate_acceptance_of(:terms_of_service). with_message('You must accept the terms of service') end end # Minitest (Shoulda) class RegistrationTest < ActiveSupport::TestCase should validate_acceptance_of(:terms_of_service). with_message('You must accept the terms of service') end
@return [ValidateAcceptanceOfMatcher]
# File lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb, line 78 def validate_acceptance_of(attr) ValidateAcceptanceOfMatcher.new(attr) end
The `validate_confirmation_of` matcher tests usage of the `validates_confirmation_of` validation.
class User include ActiveModel::Model attr_accessor :email validates_confirmation_of :email end # RSpec describe User do it { should validate_confirmation_of(:email) } end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_confirmation_of(:email) end
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class User include ActiveModel::Model attr_accessor :password validates_confirmation_of :password, on: :create end # RSpec describe User do it { should validate_confirmation_of(:password).on(:create) } end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_confirmation_of(:password).on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class User include ActiveModel::Model attr_accessor :password validates_confirmation_of :password, message: 'Please re-enter your password' end # RSpec describe User do it do should validate_confirmation_of(:password). with_message('Please re-enter your password') end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_confirmation_of(:password). with_message('Please re-enter your password') end
@return [ValidateConfirmationOfMatcher]
# File lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb, line 75 def validate_confirmation_of(attr) ValidateConfirmationOfMatcher.new(attr) end
The `validate_exclusion_of` matcher tests usage of the `validates_exclusion_of` validation, asserting that an attribute cannot take a blacklist of values, and inversely, can take values outside of this list.
If your blacklist is an array of values, use `in_array`:
class Game include ActiveModel::Model attr_accessor :supported_os validates_exclusion_of :supported_os, in: ['Mac', 'Linux'] end # RSpec describe Game do it do should validate_exclusion_of(:supported_os). in_array(['Mac', 'Linux']) end end # Minitest (Shoulda) class GameTest < ActiveSupport::TestCase should validate_exclusion_of(:supported_os). in_array(['Mac', 'Linux']) end
If your blacklist is a range of values, use `in_range`:
class Game include ActiveModel::Model attr_accessor :supported_os validates_exclusion_of :supported_os, in: ['Mac', 'Linux'] end # RSpec describe Game do it do should validate_exclusion_of(:floors_with_enemies). in_range(5..8) end end # Minitest (Shoulda) class GameTest < ActiveSupport::TestCase should validate_exclusion_of(:floors_with_enemies). in_range(5..8) end
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class Game include ActiveModel::Model attr_accessor :weapon validates_exclusion_of :weapon, in: ['pistol', 'paintball gun', 'stick'], on: :create end # RSpec describe Game do it do should validate_exclusion_of(:weapon). in_array(['pistol', 'paintball gun', 'stick']). on(:create) end end # Minitest (Shoulda) class GameTest < ActiveSupport::TestCase should validate_exclusion_of(:weapon). in_array(['pistol', 'paintball gun', 'stick']). on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class Game include ActiveModel::Model attr_accessor :weapon validates_exclusion_of :weapon, in: ['pistol', 'paintball gun', 'stick'], message: 'You chose a puny weapon' end # RSpec describe Game do it do should validate_exclusion_of(:weapon). in_array(['pistol', 'paintball gun', 'stick']). with_message('You chose a puny weapon') end end # Minitest (Shoulda) class GameTest < ActiveSupport::TestCase should validate_exclusion_of(:weapon). in_array(['pistol', 'paintball gun', 'stick']). with_message('You chose a puny weapon') end
@return [ValidateExclusionOfMatcher]
# File lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb, line 117 def validate_exclusion_of(attr) ValidateExclusionOfMatcher.new(attr) end
The `validate_inclusion_of` matcher tests usage of the `validates_inclusion_of` validation, asserting that an attribute can take a whitelist of values and cannot take values outside of this list.
If your whitelist is an array of values, use `in_array`:
class Issue include ActiveModel::Model attr_accessor :state validates_inclusion_of :state, in: ['open', 'resolved', 'unresolved'] end # RSpec describe Issue do it do should validate_inclusion_of(:state). in_array(['open', 'resolved', 'unresolved']) end end # Minitest (Shoulda) class IssueTest < ActiveSupport::TestCase should validate_inclusion_of(:state). in_array(['open', 'resolved', 'unresolved']) end
If your whitelist is a range of values, use `in_range`:
class Issue include ActiveModel::Model attr_accessor :priority validates_inclusion_of :priority, in: 1..5 end # RSpec describe Issue do it { should validate_inclusion_of(:state).in_range(1..5) } end # Minitest (Shoulda) class IssueTest < ActiveSupport::TestCase should validate_inclusion_of(:state).in_range(1..5) end
#### Caveats
We discourage using `validate_inclusion_of` with boolean columns. In fact, there is never a case where a boolean column will be anything but true, false, or nil, as ActiveRecord will type-cast an incoming value to one of these three values. That means there isn't any way we can refute this logic in a test. Hence, this will produce a warning:
it do should validate_inclusion_of(:imported). in_array([true, false]) end
The only case where `validate_inclusion_of` could be appropriate is for ensuring that a boolean column accepts nil, but we recommend using `allow_value` instead, like this:
it { should allow_value(nil).for(:imported) }
#### Qualifiers
Use `on` if your validation applies only under a certain context.
class Issue include ActiveModel::Model attr_accessor :severity validates_inclusion_of :severity, in: %w(low medium high), on: :create end # RSpec describe Issue do it do should validate_inclusion_of(:severity). in_array(%w(low medium high)). on(:create) end end # Minitest (Shoulda) class IssueTest < ActiveSupport::TestCase should validate_inclusion_of(:severity). in_array(%w(low medium high)). on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class Issue include ActiveModel::Model attr_accessor :severity validates_inclusion_of :severity, in: %w(low medium high), message: 'Severity must be low, medium, or high' end # RSpec describe Issue do it do should validate_inclusion_of(:severity). in_array(%w(low medium high)). with_message('Severity must be low, medium, or high') end end # Minitest (Shoulda) class IssueTest < ActiveSupport::TestCase should validate_inclusion_of(:severity). in_array(%w(low medium high)). with_message('Severity must be low, medium, or high') end
##### with_low_message
Use `with_low_message` if you have a custom validation message for when a given value is too low.
class Person include ActiveModel::Model attr_accessor :age validate :age_must_be_valid private def age_must_be_valid if age < 65 self.errors.add :age, 'You do not receive any benefits' end end end # RSpec describe Person do it do should validate_inclusion_of(:age). in_range(0..65). with_low_message('You do not receive any benefits') end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_inclusion_of(:age). in_range(0..65). with_low_message('You do not receive any benefits') end
##### with_high_message
Use `with_high_message` if you have a custom validation message for when a given value is too high.
class Person include ActiveModel::Model attr_accessor :age validate :age_must_be_valid private def age_must_be_valid if age > 21 self.errors.add :age, "You're too old for this stuff" end end end # RSpec describe Person do it do should validate_inclusion_of(:age). in_range(0..21). with_high_message("You're too old for this stuff") end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_inclusion_of(:age). in_range(0..21). with_high_message("You're too old for this stuff") end
##### allow_nil
Use `allow_nil` to assert that the attribute allows nil.
class Issue include ActiveModel::Model attr_accessor :state validates_presence_of :state validates_inclusion_of :state, in: ['open', 'resolved', 'unresolved'], allow_nil: true end # RSpec describe Issue do it do should validate_inclusion_of(:state). in_array(['open', 'resolved', 'unresolved']). allow_nil end end # Minitest (Shoulda) class IssueTest < ActiveSupport::TestCase should validate_inclusion_of(:state). in_array(['open', 'resolved', 'unresolved']). allow_nil end
##### allow_blank
Use `allow_blank` to assert that the attribute allows blank.
class Issue include ActiveModel::Model attr_accessor :state validates_presence_of :state validates_inclusion_of :state, in: ['open', 'resolved', 'unresolved'], allow_blank: true end # RSpec describe Issue do it do should validate_inclusion_of(:state). in_array(['open', 'resolved', 'unresolved']). allow_blank end end # Minitest (Shoulda) class IssueTest < ActiveSupport::TestCase should validate_inclusion_of(:state). in_array(['open', 'resolved', 'unresolved']). allow_blank end
@return [ValidateInclusionOfMatcher]
# File lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb, line 265 def validate_inclusion_of(attr) ValidateInclusionOfMatcher.new(attr) end
The `validate_length_of` matcher tests usage of the `validates_length_of` matcher. Note that this matcher is intended to be used against string columns and not integer columns.
#### Qualifiers
Use `on` if your validation applies only under a certain context.
class User include ActiveModel::Model attr_accessor :password validates_length_of :password, minimum: 10, on: :create end # RSpec describe User do it do should validate_length_of(:password). is_at_least(10). on(:create) end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:password). is_at_least(10). on(:create) end
##### is_at_least
Use `is_at_least` to test usage of the `:minimum` option. This asserts that the attribute can take a string which is equal to or longer than the given length and cannot take a string which is shorter.
class User include ActiveModel::Model attr_accessor :bio validates_length_of :bio, minimum: 15 end # RSpec describe User do it { should validate_length_of(:bio).is_at_least(15) } end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:bio).is_at_least(15) end
##### is_at_most
Use `is_at_most` to test usage of the `:maximum` option. This asserts that the attribute can take a string which is equal to or shorter than the given length and cannot take a string which is longer.
class User include ActiveModel::Model attr_accessor :status_update validates_length_of :status_update, maximum: 140 end # RSpec describe User do it { should validate_length_of(:status_update).is_at_most(140) } end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:status_update).is_at_most(140) end
##### is_equal_to
Use `is_equal_to` to test usage of the `:is` option. This asserts that the attribute can take a string which is exactly equal to the given length and cannot take a string which is shorter or longer.
class User include ActiveModel::Model attr_accessor :favorite_superhero validates_length_of :favorite_superhero, is: 6 end # RSpec describe User do it { should validate_length_of(:favorite_superhero).is_equal_to(6) } end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:favorite_superhero).is_equal_to(6) end
##### is_at_least + is_at_most
Use `is_at_least` and `is_at_most` together to test usage of the `:in` option.
class User include ActiveModel::Model attr_accessor :password validates_length_of :password, in: 5..30 end # RSpec describe User do it do should validate_length_of(:password). is_at_least(5).is_at_most(30) end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:password). is_at_least(5).is_at_most(30) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class User include ActiveModel::Model attr_accessor :password validates_length_of :password, minimum: 10, message: "Password isn't long enough" end # RSpec describe User do it do should validate_length_of(:password). is_at_least(10). with_message("Password isn't long enough") end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:password). is_at_least(10). with_message("Password isn't long enough") end
##### with_short_message
Use `with_short_message` if you are using a custom “too short” message.
class User include ActiveModel::Model attr_accessor :secret_key validates_length_of :secret_key, in: 15..100, too_short: 'Secret key must be more than 15 characters' end # RSpec describe User do it do should validate_length_of(:secret_key). is_at_least(15). with_short_message('Secret key must be more than 15 characters') end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:secret_key). is_at_least(15). with_short_message('Secret key must be more than 15 characters') end
##### with_long_message
Use `with_long_message` if you are using a custom “too long” message.
class User include ActiveModel::Model attr_accessor :secret_key validates_length_of :secret_key, in: 15..100, too_long: 'Secret key must be less than 100 characters' end # RSpec describe User do it do should validate_length_of(:secret_key). is_at_most(100). with_long_message('Secret key must be less than 100 characters') end end # Minitest (Shoulda) class UserTest < ActiveSupport::TestCase should validate_length_of(:secret_key). is_at_most(100). with_long_message('Secret key must be less than 100 characters') end
@return [ValidateLengthOfMatcher]
# File lib/shoulda/matchers/active_model/validate_length_of_matcher.rb, line 221 def validate_length_of(attr) ValidateLengthOfMatcher.new(attr) end
The `validate_numericality_of` matcher tests usage of the `validates_numericality_of` validation.
class Person include ActiveModel::Model attr_accessor :gpa validates_numericality_of :gpa end # RSpec describe Person do it { should validate_numericality_of(:gpa) } end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:gpa) end
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class Person include ActiveModel::Model attr_accessor :number_of_dependents validates_numericality_of :number_of_dependents, on: :create end # RSpec describe Person do it do should validate_numericality_of(:number_of_dependents). on(:create) end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:number_of_dependents).on(:create) end
##### only_integer
Use `only_integer` to test usage of the `:only_integer` option. This asserts that your attribute only allows integer numbers and disallows non-integer ones.
class Person include ActiveModel::Model attr_accessor :age validates_numericality_of :age, only_integer: true end # RSpec describe Person do it { should validate_numericality_of(:age).only_integer } end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:age).only_integer end
##### is_less_than
Use `is_less_than` to test usage of the the `:less_than` option. This asserts that the attribute can take a number which is less than the given value and cannot take a number which is greater than or equal to it.
class Person include ActiveModel::Model attr_accessor :number_of_cars validates_numericality_of :number_of_cars, less_than: 2 end # RSpec describe Person do it do should validate_numericality_of(:number_of_cars). is_less_than(2) end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:number_of_cars). is_less_than(2) end
##### is_less_than_or_equal_to
Use `is_less_than_or_equal_to` to test usage of the `:less_than_or_equal_to` option. This asserts that the attribute can take a number which is less than or equal to the given value and cannot take a number which is greater than it.
class Person include ActiveModel::Model attr_accessor :birth_year validates_numericality_of :birth_year, less_than_or_equal_to: 1987 end # RSpec describe Person do it do should validate_numericality_of(:birth_year). is_less_than_or_equal_to(1987) end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:birth_year). is_less_than_or_equal_to(1987) end
##### is_equal_to
Use `is_equal_to` to test usage of the `:equal_to` option. This asserts that the attribute can take a number which is equal to the given value and cannot take a number which is not equal.
class Person include ActiveModel::Model attr_accessor :weight validates_numericality_of :weight, equal_to: 150 end # RSpec describe Person do it { should validate_numericality_of(:weight).is_equal_to(150) } end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:weight).is_equal_to(150) end
##### is_greater_than_or_equal_to
Use `is_greater_than_or_equal_to` to test usage of the `:greater_than_or_equal_to` option. This asserts that the attribute can take a number which is greater than or equal to the given value and cannot take a number which is less than it.
class Person include ActiveModel::Model attr_accessor :height validates_numericality_of :height, greater_than_or_equal_to: 55 end # RSpec describe Person do it do should validate_numericality_of(:height). is_greater_than_or_equal_to(55) end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:height). is_greater_than_or_equal_to(55) end
##### is_greater_than
Use `is_greater_than` to test usage of the `:greater_than` option. This asserts that the attribute can take a number which is greater than the given value and cannot take a number less than or equal to it.
class Person include ActiveModel::Model attr_accessor :legal_age validates_numericality_of :legal_age, greater_than: 21 end # RSpec describe Person do it do should validate_numericality_of(:legal_age). is_greater_than(21) end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:legal_age). is_greater_than(21) end
##### even
Use `even` to test usage of the `:even` option. This asserts that the attribute can take odd numbers and cannot take even ones.
class Person include ActiveModel::Model attr_accessor :birth_month validates_numericality_of :birth_month, even: true end # RSpec describe Person do it { should validate_numericality_of(:birth_month).even } end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:birth_month).even end
##### odd
Use `odd` to test usage of the `:odd` option. This asserts that the attribute can take a number which is odd and cannot take a number which is even.
class Person include ActiveModel::Model attr_accessor :birth_day validates_numericality_of :birth_day, odd: true end # RSpec describe Person do it { should validate_numericality_of(:birth_day).odd } end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:birth_day).odd end
##### #with_message
Use `with_message` if you are using a custom validation message.
class Person include ActiveModel::Model attr_accessor :number_of_dependents validates_numericality_of :number_of_dependents, message: 'Number of dependents must be a number' end # RSpec describe Person do it do should validate_numericality_of(:number_of_dependents). with_message('Number of dependents must be a number') end end # Minitest (Shoulda) class PersonTest < ActiveSupport::TestCase should validate_numericality_of(:number_of_dependents). with_message('Number of dependents must be a number') end
##### allow_nil
Use `allow_nil` to assert that the attribute allows nil.
class Post include ActiveModel::Model attr_accessor :age validates_numericality_of :age, allow_nil: true end # RSpec describe Post do it { should validate_numericality_of(:age).allow_nil } end # Minitest (Shoulda) class PostTest < ActiveSupport::TestCase should validate_numericality_of(:age).allow_nil end
@return [ValidateNumericalityOfMatcher]
# File lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb, line 301 def validate_numericality_of(attr) ValidateNumericalityOfMatcher.new(attr) end
The `validate_presence_of` matcher tests usage of the `validates_presence_of` validation.
class Robot include ActiveModel::Model attr_accessor :arms validates_presence_of :arms end # RSpec describe Robot do it { should validate_presence_of(:arms) } end # Minitest (Shoulda) class RobotTest < ActiveSupport::TestCase should validate_presence_of(:arms) end
#### Caveats
Under Rails 4 and greater, if your model `has_secure_password` and you are validating presence of the password using a record whose password has already been set prior to calling the matcher, you will be instructed to use a record whose password is empty instead.
For example, given this scenario:
class User < ActiveRecord::Base has_secure_password validations: false validates_presence_of :password end describe User do subject { User.new(password: '123456') } it { should validate_presence_of(:password) } end
the above test will raise an error like this:
The validation failed because your User model declares `has_secure_password`, and `validate_presence_of` was called on a user which has `password` already set to a value. Please use a user with an empty `password` instead.
This happens because `has_secure_password` itself overrides your model so that it is impossible to set `password` to nil. This means that it is impossible to test that setting `password` to nil places your model in an invalid state (which in turn means that the validation itself is unnecessary).
#### Qualifiers
##### on
Use `on` if your validation applies only under a certain context.
class Robot include ActiveModel::Model attr_accessor :arms validates_presence_of :arms, on: :create end # RSpec describe Robot do it { should validate_presence_of(:arms).on(:create) } end # Minitest (Shoulda) class RobotTest < ActiveSupport::TestCase should validate_presence_of(:arms).on(:create) end
##### #with_message
Use `with_message` if you are using a custom validation message.
class Robot include ActiveModel::Model attr_accessor :legs validates_presence_of :legs, message: 'Robot has no legs' end # RSpec describe Robot do it do should validate_presence_of(:legs). with_message('Robot has no legs') end end # Minitest (Shoulda) class RobotTest < ActiveSupport::TestCase should validate_presence_of(:legs). with_message('Robot has no legs') end
@return [ValidatePresenceOfMatcher]
# File lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb, line 108 def validate_presence_of(attr) ValidatePresenceOfMatcher.new(attr) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 351 def with_message(message, given_options = {}) if message.present? @expects_custom_validation_message = true options[:expected_message] = message options[:expected_message_values] = given_options.fetch(:values, {}) if given_options.key?(:against) @attribute_to_check_message_against = given_options[:against] end end self end
Protected Instance Methods
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 566 def attribute_changed_value_message @attribute_changed_value_message || method(:default_attribute_changed_value_message) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 595 def attribute_setters_and_validators_for_values_to_set @_attribute_setters_and_validators_for_values_to_set ||= AttributeSettersAndValidators.new( self, values_to_set.map { |value| [attribute_to_set, value] } ) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 590 def attribute_setters_for_values_to_preset @_attribute_setters_for_values_to_preset ||= AttributeSetters.new(self, values_to_preset) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 571 def default_attribute_changed_value_message "As indicated in the message above, :#{result.attribute_setter.attribute_name} seems to be changing certain values as they are set, and this could have something to do with why this test is failing. If you've overridden the writer method for this attribute, then you may need to change it to make this test pass, or do something else entirely. ".strip end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 618 def default_attribute_message default_error_message( options[:expected_message], default_attribute_message_values ) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 625 def default_attribute_message_values defaults = { model_name: model_name, instance: instance, attribute: attribute_to_check_message_against, } defaults.merge(options[:expected_message_values]) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 610 def default_expected_message if expects_strict? "#{human_attribute_name} #{default_attribute_message}" else default_attribute_message end end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 540 def default_failure_message_preface ''.tap do |preface| if descriptions_for_preset_values.any? preface << 'After setting ' preface << descriptions_for_preset_values.to_sentence preface << ', then ' else preface << 'After ' end preface << 'setting ' preface << description_for_resulting_attribute_setter unless preface.end_with?('--') preface << ',' end preface << " the matcher expected the #{model.name} to be" end end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 586 def description_for_resulting_attribute_setter result.attribute_setter_description end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 581 def descriptions_for_preset_values attribute_setters_for_values_to_preset. map(&:attribute_setter_description) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 536 def failure_message_preface @failure_message_preface || method(:default_failure_message_preface) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 639 def human_attribute_name instance.class.human_attribute_name( attribute_to_check_message_against ) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 561 def include_attribute_changed_value_message? !ignore_interference_by_writer.never? && result.attribute_setter.attribute_changed_value? end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 603 def inspected_values_to_set Shoulda::Matchers::Util.inspect_values(values_to_set).to_sentence( two_words_connector: " or ", last_word_connector: ", or " ) end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 635 def model_name instance.class.to_s.underscore end
# File lib/shoulda/matchers/active_model/allow_value_matcher.rb, line 531 def run(strategy) attribute_setters_for_values_to_preset.first_failing || attribute_setters_and_validators_for_values_to_set.public_send(strategy) end