Method: Shoulda::Matchers::ActiveRecord#validate_uniqueness_of
- Defined in:
- lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb
#validate_uniqueness_of(attr) ⇒ ValidateUniquenessOfMatcher
The validate_uniqueness_of matcher tests usage of the
validates_uniqueness_of validation. It first checks for an existing
instance of your model in the database, creating one if necessary. It
then takes a new instance of that model and asserts that it fails
validation if the attribute or attributes you've specified in the
validation are set to values which are the same as those of the
pre-existing record (thereby failing the uniqueness check).
class Post < ActiveRecord::Base
validates :permalink, uniqueness: true
end
# RSpec
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:permalink) }
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:permalink)
end
Caveat
This matcher works a bit differently than other matchers. As noted
before, it will create an instance of your model if one doesn't already
exist. Sometimes this step fails, especially if you have database-level
restrictions on any attributes other than the one which is unique. In
this case, the solution is to populate these attributes with values
before you call validate_uniqueness_of.
For example, say you have the following migration and model:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :content, null: false
end
end
end
class Post < ActiveRecord::Base
validates :title, uniqueness: true
end
You may be tempted to test the model like this:
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:title) }
end
However, running this test will fail with an exception such as:
Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
validate_uniqueness_of works by matching a new record against an
existing record. If there is no existing record, it will create one
using the record you provide.
While doing this, the following error was raised:
PG::NotNullViolation: ERROR: null value in column "content" violates not-null constraint
DETAIL: Failing row contains (1, null, null).
: INSERT INTO "posts" DEFAULT VALUES RETURNING "id"
The best way to fix this is to provide the matcher with a record where
any required attributes are filled in with valid values beforehand.
(The exact error message will differ depending on which database you're using, but you get the idea.)
This happens because validate_uniqueness_of tries to create a new post
but cannot do so because of the content attribute: though unrelated to
this test, it nevertheless needs to be filled in. As indicated at the
end of the error message, the solution is to build a custom Post object
ahead of time with content filled in:
RSpec.describe Post, type: :model do
describe "validations" do
subject { Post.new(content: "Here is the content") }
it { should validate_uniqueness_of(:title) }
end
end
Or, if you're using
FactoryBot and you have a
post factory defined which automatically fills in content, you can
say:
RSpec.describe Post, type: :model do
describe "validations" do
subject { FactoryBot.build(:post) }
it { should validate_uniqueness_of(:title) }
end
end
Qualifiers
Use on if your validation applies only under a certain context.
class Post < ActiveRecord::Base
validates :title, uniqueness: true, on: :create
end
# RSpec
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:title).on(:create) }
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:title).on(:create)
end
with_message
Use with_message if you are using a custom validation message.
class Post < ActiveRecord::Base
validates :title, uniqueness: true, message: 'Please choose another title'
end
# RSpec
RSpec.describe Post, type: :model do
it do
should validate_uniqueness_of(:title).
('Please choose another title')
end
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:title).
('Please choose another title')
end
scoped_to
Use scoped_to to test usage of the :scope option. This asserts that
a new record fails validation if not only the primary attribute is not
unique, but the scoped attributes are not unique either.
class Post < ActiveRecord::Base
validates :slug, uniqueness: { scope: :journal_id }
end
# RSpec
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:slug).scoped_to(:journal_id)
end
NOTE: Support for testing uniqueness validation scoped to an array of associations is not available.
For more information, please refer to https://github.com/thoughtbot/shoulda-matchers/issues/814
case_insensitive
Use case_insensitive to test usage of the :case_sensitive option
with a false value. This asserts that the uniquable attributes fail
validation even if their values are a different case than corresponding
attributes in the pre-existing record.
class Post < ActiveRecord::Base
validates :key, uniqueness: { case_sensitive: false }
end
# RSpec
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:key).case_insensitive }
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:key).case_insensitive
end
ignoring_case_sensitivity
By default, validate_uniqueness_of will check that the
validation is case sensitive: it asserts that uniquable attributes pass
validation when their values are in a different case than corresponding
attributes in the pre-existing record.
Use ignoring_case_sensitivity to skip this check. This qualifier is
particularly handy if your model has somehow changed the behavior of
attribute you're testing so that it modifies the case of incoming values
as they are set. For instance, perhaps you've overridden the writer
method or added a before_validation callback to normalize the
attribute.
class User < ActiveRecord::Base
validates :email, uniqueness: true
def email=(value)
super(value.downcase)
end
end
# RSpec
RSpec.describe Post, type: :model do
it do
should validate_uniqueness_of(:email).ignoring_case_sensitivity
end
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:email).ignoring_case_sensitivity
end
allow_nil
Use allow_nil to assert that the attribute allows nil.
class Post < ActiveRecord::Base
validates :author_id, uniqueness: true, allow_nil: true
end
# RSpec
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:author_id).allow_nil }
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:author_id).allow_nil
end
allow_blank
Use allow_blank to assert that the attribute allows a blank value.
class Post < ActiveRecord::Base
validates :author_id, uniqueness: true, allow_blank: true
end
# RSpec
RSpec.describe Post, type: :model do
it { should validate_uniqueness_of(:author_id).allow_blank }
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:author_id).allow_blank
end
alternatives
Use alternatives to specify alternative valid values to use
for testing uniqueness.
class Post < ActiveRecord::Base
validates :title, uniqueness: true
end
# RSpec
RSpec.describe Post, type: :model do
it do
should validate_uniqueness_of(:title).alternatives('Alternative Title')
end
end
# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:title).alternatives(['Alternative Title', 'Another Title'])
end
291 292 293 |
# File 'lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb', line 291 def validate_uniqueness_of(attr) ValidateUniquenessOfMatcher.new(attr) end |