I would like to test the uniquness of User model.
My User model class looks like:
class User
include Mongoid::Document
field :email, type: String
embeds_one :details
validates :email,
presence: true,
uniqueness: true,
format: {
with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z0-9]{2,})\Z/i,
on: :create
},
length: { in: 6..50 }
end
My rspec test which belongs to the model looks like:
...
before(:each) do
FactoryGirl.create(:user, email: taken_mail)
end
it "with an already used email" do
expect(FactoryGirl.create(:user, email: taken_mail)).to_not be_valid
end
After I executed bundle exec rspec it always raises the next error instead of passed with success:
Failure/Error: expect(FactoryGirl.create(:user, email: taken_mail)).to_not be_valid
Mongoid::Errors::Validations:
Problem:
Validation of User failed.
Summary:
The following errors were found: Email is already taken
Resolution:
Try persisting the document with valid data or remove the validations.
If I use this it passes with success:
it { should validate_uniqueness_of(:email) }
I would like to use expect(...). Can anybody help me out?
The issue is you are trying to persist an invalid object into the database, which throws an exception and breaks the test (because email is not unique), before even the test is done using the expect method.
The correct way is to use build here instead of create, which doesn't persist the object in the database, by building the record only in memory and allowing your test to do its job. Therefore to fix it:
expect(FactoryGirl.build(:user, email: taken_mail)).to_not be_valid
Also note that is better to use build rather than create if you don't need to actually save the record in the database, since it's a cheaper operation and you will get the same outcome, unless for some reason your record must be saved to the database for your tests to work in a way you want them, such as saving the first record in in your example.
Related
In my Rails app, I am trying to save MAC addresses for devices belonging to different users. Each MAC address must be unique, so I included the uniqueness validation.
The validation itself seems to be working, since duplicate records were rejected when I tried using the Rails Console (ActiveRecord::RecordNotUnique). However, my test to check that only unique records can be saved is failing.
So my questions are:
Why is my test failing and how can I fix it?
I read elsewhere that the uniqueness validation alone is not a reliable way to assure uniqueness. Should I use other methods, such as before_save callbacks?
This is the error message I'm getting for the test:
Expected #<MacAddress id: nil, user_id: nil, address: "MACADD123", created_at: nil, updated_at: nil> to be nil or false
Setup in my model files:
# app/models/mac_address.rb
class MacAddress < ApplicationRecord
validates :address, uniqueness: true
belongs_to :user
end
# app/models/user.rb
class User < ApplicationRecord
has_many :mac_addresses, dependent: :destroy
end
Test to check for uniqueness:
class MacAddressTest < ActiveSupport::TestCase
test 'mac address must be unique' do
new_mac = 'MACADD123'
assert MacAddress.create(user: User.first, address: new_mac)
assert MacAddress.all.pluck(:address).include?(new_mac)
# The 'assert_not' below is failing.
assert_not MacAddress.create(user: User.second, address: new_mac)
end
end
Thanks for any help in advance.
As per the documentation on create:
Notice there's no id for that record. It hasn't been persisted. Check for errors with .errors.full_messages to see the uniqueness validation failure.
The resulting object is returned whether the object was saved successfully to the database or not.
You should assert that it's saved, like:
mac_address = MacAddress.create(...)
assert !mac_address.new_record?
Where that tells you if it's been saved or not. Alternatively you can use create! which will raise ActiveRecord::RecordInvalid if it failed.
For future reference and for anyone viewing this question - I rewrote my test with save instead of create like below:
test 'mac address must be unique' do
test_address = 'MACADD123'
original_mac = MacAddress.new(user: User.first, address: test_address)
duplicate_mac = MacAddress.new(user: User.second, address: test_address)
assert original_mac.save
assert MacAddress.pluck(:address).include?(test_address)
assert_not duplicate_mac.save
duplicate_mac.errors.messages[:address].include?('has already been taken')
end
I have a User model with an email attribute. Various parts of my app conceive of an "email" differently; sometimes as a string, sometimes as a hash ({ token: 'foo', host: 'bar.com' }), sometimes as an object. This is bad; I want the concept of an email to be consistent wherever I use it.
So, I use an Email object that does what I want. I don't see any good reason to create an Email table; instead, I just want to create a new Email object corresponding to an email string whenever I need one. Therefore User looks like this:
class User < ActiveRecord::Base
def email
Email.new(read_attribute :email)
end
def email= email
write_attribute :email, email.to_s
end
end
However, this causes at least two issues:
I can't search for a user by email without an explicit call to to_s.
I can't run a uniqueness validation on the email column anymore. I get a TypeError: can't cast Email to string. (I can fix this with a custom validator.)
Questions:
Is there something wrong with this approach? The fact that it breaks my validation is a code smell to me.
Is there some way to get the existing validates :email, uniqueness: { case_sensitive: false } validation to work with these new accessor definitions?
I am trying to require unique email addresses for a record in my RoR project, I have the following validation in place:
validates :email, presence: { :message => "You must provide an email address." }, uniqueness: { :message => "This email is already taken." }
validates_uniqueness_of :email, :message=>"This email is already taken"
Next to the form to add a record is a list of recently added entries. When I try to save a new record it renders the form over with an error however if you look at the list the item (with the duplicate email address) has been added. If you refresh the page or change the email in the form and resubmit the entry disappears from the list. I'm curious why it seems like the record is being saved even though the validation is firing properly.
I had thought it could be that I was creating the object with object.create(params) however when I changed that to object.build(params) it had no effect. All help is appreciated, thanks!
This is not a problem with your validation, but with the way how you render that list.
If you add your object to the list even if the validation was not successful than that element will be rendered exactly like all other elements in that list (since it has all nessessary values).
You can use the following methods to exclude such elements from a list or to handle them in a different way - grey out for example:
record.valid? # returns true if the record is valid
record.persisted? # returns true if the record exists in the database (was save)
whereas:
record.new_record? # returns true if the record wasn't saved into the database
By the way: You mix up the new and the old hash syntax in your validator definitions and the uniqueness validator is defined twice. You can change that to:
validates :email, presence: { message: 'You must provide an email address.' },
uniqueness: { message: 'This email is already taken.' }
create method creates an object and saves it to the database;
build method(Alias for: new) won't "create" a record in database, just create a new object in memory.
Validations are used to ensure that only valid data is saved into your database, validations are run before the record are sent to the database. Only Some methods will trigger validations.
create
create!
save
save!
update
update!
I am working on adding some new validation to an app. The entire idea is to make sure that when a user updates their username it does not violate our username policy.
This is the current incantation of the validator:
validates_format_of :username, with: /[0-9a-zA-Z_]+/,
on: :update,
if: lambda { |u| u.username_changed? }
Even with this validation bad characters make it through.
Here is my spec that I am using:
it "validates and does not update a user with an invalid username" do
user.update_attributes(username: "k~!tten")
expect(user.username).not_to eq "k~!tten"
end
Any help with this is greatly appreciated.
Username is "k~!tten" only on the model. It has not been saved to the database due to validation failure. Instead of:
expect(user.username).not_to eq "k~!tten"
use the below to assert that the username does not pass validation:
expect(user.username).not_to be_valid
I am using Ruby on Rails 3.0.9 and RSpec 2. I would like to know what "validation logic" I should test. That is, if in my model I have:
class User < ActiveRecord::Base
validates :firstname
:presence => true
end
What should I test of the following?
"should have a valid first name"
"should not have a valid first name"
Or should I test both?
You can test both by simply doing this:
it "should validate presence of" do
should validate_presence_of :firstname
end
Take a look at the shoulda matchers for all such standard Rails Validation.
I think you should not test both. This will be enough:
describe User do
it { should validate_presence_of(:firstname) }
end
There is basically no algorithm for validating names, because the form of names is incredibly culture-centric. So, really you should avoid complex validations for something like a person's name. Some places/cultures don't have last names, for example, so even validating their presence isn't proper. There's a whole list of other examples that make validating names a really bad idea. For more information on the issue of validating names itself, see my answer to this question.
That being said, in general, when validating any field, I test both valid and invalid data. I make sure that, when I set a field to a valid value, that the .valid? method returns true, and when it's invalid, that it returns false.
Typically you don't need to do a long list, you just need to test
A typical valid and invalid example
A few edge cases
you can also test for specific values:
describe User do
context "validations" do
it { should_not allow_value("admin").for(:firstname) }
it { should allow_value("JohnDoe").for(:firstname) }
end
end