This is the Consumer class with validations
class Consumer < ActiveRecord::Base
validates :email, format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create, message: 'invalid email format'}, length: { maximum: 256 }
This is what I used to test whether a consumer's email is in the valid format or not but I am getting errors. Is there a better way to test regexes other than this method?
it 'email with invalid format is invalid' do
consumer = Consumer.new(email: 'bugs', password: '12345')
consumer.valid?
consumer.errors.full_messages
expect(consumer.errors.get(:email)).to eq(['email is not valid'])
end
it 'has no errors for valid email format' do
consumer = Consumer.new(email: 'bugs#disney.com', password: '12345')
consumer.valid?
consumer.errors.full_messages
expect(consumer.errors.get(:email)).to be_nil
end
You can simplify your test if you guarantee that all other values of your Consumer object are valid.
For example, lets say your Customer has email, password, name, last_name; then you can test this way:
it 'email with invalid format is invalid' do
consumer = Consumer.new(email: 'bugs', password: '12345', name: 'John', last_name: 'Doe')
expect(consumer.valid?).to be(false)
end
In this example all attributes are correct, except email, so just checking if valid? returns false will be enough.
Related
Model:
validates :email,
uniqueness: {
message: "has been taken."
},
presence: {
message: "cannot be blank."
},
length: {
minimum: 3,
message: "is too short, must be a minimum of 3 characters.",
allow_blank: true
},
format: {
with: /\A[A-Z0-9_\.&%\+\-']+#(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,13})\z/i,
message: "is invalid.",
if: Proc.new { |u| u.email && u.email.length >= 3 }
}
RSpec:
before(:each) do
#user = FactoryGirl.build(:user)
end
it { should validate_length_of(:email).is_at_least(3) }
Error:
Failure/Error: should validate_length_of(:email).is_at_least(3)
Expected errors to include "is too short (minimum is 3 characters)" when email is set to "xx",
got errors:
* "is too short (minimum is 4 characters)" (attribute: password, value: nil)
* "is too short, must be a minimum of 3 characters." (attribute: email, value: "xx")
* "is not included in the list" (attribute: state, value: "passive")
Factory:
factory :user, class: User do
email FFaker::Internet.email
password FFaker::Internet.password
username FFaker::Internet.user_name
end
I am using factory_girl_rails with shoulda_matchers. Every time I try to validate my email, I keep getting an error like above. It says the email value is "xx", but the length of the factory email is greater than that. How can I write an rspec that will pass?
Shoulda-matchers tests validations by testing the messages in the errors object.
The valdation matchers only work with the rails default error messages unless you specify otherwise:
it { should validate_length_of(:email).with_message("is too short, must be a minimum of 3 characters.") }
The with_message method is detailed in this comment.
You are understanding the error (and it's cause) wrong. The error says that it expects "is too short (minimum is 3 characters)" but in the errors it can't find that string (rspec finds "is too short, must be a minimum of 3 characters." which is what you defined on your validation).
When you use shoulda matchers and say
it { should validate_length_of(:email).is_at_least(3) }
I'm guessing it creates a test with an email shorter than 3 and checks if it fails, that's why it's ignoring your factory, internally should matcher is setting a fixed length string just to make that test pass.
When you see the errors in user, that test should actually work since the error is actually there, only the string is different. So, you have two options: remove the custom message when length is minimum: 3; or tell the matcher what message you are expecting:
it { should validate_length_of(:email).is_at_least(3).with_message("is too short, must be a minimum of 3 characters.") }
I'm writing a Rails integration test that checks that the user's title saves. The title has one validation: it has to be no more than 255 characters. But #user.update_attributes!(title: params[:title]) is throwing the error "Password must have at least 6 characters." But...I'm not updating the password or anything other than the title. So how do I save this attribute with its own validation and not worry about the password?
Test:
test "profile submits new title and description successfully" do
log_in_as(#non_admin)
get user_path(#non_admin)
assert_nil #non_admin.title
post "/users/#{#non_admin.id}/update_description",
{ title: "I am a man of constant sorrow." }
user = assigns(:user)
user.reload.title
assert user.title == "I am a man of constant sorrow."
assert_template 'users/show'
assert flash[:success]
end
Controller method (not finished, but you'll get the idea). Note, it's the update_attributes! call that throws the password validation error.
# Handles user's posted title and description.
def update_description
#user = User.find(params[:id])
# Check if title is present. If so, attempt to save, load flash, and reload.
if #user.update_attributes!(title: params[:title])
flash[:success] = "Saved title. "
# if unable, set error flash and reload.
else
flash[:warning] = "Unable to save."
end
# Same logic as before, now for description.
# Make sure two different [:success] flashes work! Probably not!
redirect_to user_path(#user)
end
Validations:
validates :password, length: { minimum: 6,
message: "must have at least 6 characters" }
validates :title, length: { maximum: 255 }
Here's the test error:
23:08:51 - INFO - Running: test/integration/users_show_test.rb
Started
ERROR["test_profile_submits_new_title_and_description_successfully", UsersShowTest, 2017-10-23 01:06:11 -0400]
test_profile_submits_new_title_and_description_successfully#UsersShowTest (1508735171.57s)
ActiveRecord::RecordInvalid: ActiveRecord::RecordInvalid: Validation failed: Password must have at least 6 characters
app/controllers/users_controller.rb:71:in `update_description'
test/integration/users_show_test.rb:22:in `block in <class:UsersShowTest>'
app/controllers/users_controller.rb:71:in `update_description'
test/integration/users_show_test.rb:22:in `block in <class:UsersShowTest>'
In case it's relevant, here's the fixture that is loaded as #non_admin:
archer:
name: Sterling Archer
email: duchess#example.gov
password_digest: <%= User.digest('Jsdfuisd8f') %>
activated: true
activated_at: <%= Time.zone.now %>
I'm a Rails noob so it's probably something basic. Thanks in advance...
UPDATE: See discussion with kasperite below. I simply needed to add on: create to my password validation.
Calling update_attributes! will triggers save!, which in turn triggers validation on the model. And since you don't provide password, it will throw the exception.
You can either do update_attribute(:title, params[:title]) which bypass validation
or this:
#user.title = params[:title]
#user.save!(validation: false)
See: http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update-21
I have a test:
test 'gift should not be saved with a non-numeric price' do
#gift = gifts(:gift_without_numeric_price)
assert_not #gift.save, 'gift was saved with non-numeric name'
end
It uses this fixture:
gift_without_numeric_price:
name: "pinecones"
description: "these pinecones smell really good"
estimated_price: "object"
link: "www.google.com"
My Gift model looks like this:
validates :estimated_price,
presence: true,
numericality: true
So I was expecting the test to pass since 'object' is not numeric, causing the #gift.save to return false and causing the assert_not to ultimately return true. However, the test fails for some reason. When I use the the direct way of creating a gift object, the test looks like this and the test passes:
test 'gift should not be saved with a non-numeric price' do
#gift = Gift.new(name: 'aa', description: 'description', link: 'www.google.com', estimated_price: 'object')
assert_not #gift.save, 'gift was saved with non-numeric name'
end
What am I doing wrong with the fixture?
You can try and change your validation:
validates :estimated_price, presence: true,numericality: { only_integer: true }
I have two attributes within my model that share the same validations
validates :first_name, :last_name, length: {minimum: 2}
Right now I have the :first_name attribute tested as follows:
RSpec.describe User, :type => :model do
it 'is invalid if the first name is less than two characters'
user = User.create(
first_name: 'a'
)
expect(user).to have(1).errors_on(:first_name)
end
For the sake of a developer who isn't familiar with how I've setup my model I wanted to explicitly state the two attributes' relationship with something like this:
it 'is invalid if the first name and/or last name has less than two characters'
user = User.create(
first_name: 'a',
last_name: 'b
)
expect(user).to have(1).errors_on(:first_name, :last_name)
Obviously this throws the error:
wrong number of arguments (2 for 0..1)
The same thing would apply if I had instituted 2 two validations:
validates :first_name, :last_name, length: {minimum: 2}, format: {with: /^([^\d\W]|[-])*$/}
And try to test for 2 errors:
it 'is invalid if the first name and/or last name has less than two characters and has special characters'
user = User.create(
first_name: '#',
last_name: '#
)
expect(user).to have(2).errors_on(:first_name, :last_name)
In RSpec 3.x, you can compound expectations with .and:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name).and have(1).errors_on(:last_name)
end
Check out the rspec-expectations documentation for more info.
For RSpec 2.x, you'll need to do one of these:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name) && have(1).errors_on(:last_name)
end
# or
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name)
expect(user).to have(1).errors_on(:last_name)
end
It's not as pretty, but it should work.
EDIT:
OP was using rspec-collection_matchers gem. That gem's custom matchers do not include RSpec 3 mixin module RSpec::Matchers::Composable, so the #and method goes unrecognized.
There are a few things to do to circumvent this issue. The easiest is to use the && technique above (in my RSpec 2.x suggestions). To use only RSpec 3 matchers, you need to use be_valid:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to_not be_valid
end
Of course, this does not distinguish between first_name errors and last_name errors as was originally intended. To do that with the be_valid matcher, you'd have to break the test into two tests:
it 'is invalid if the first name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'abc')
expect(user).to_not be_valid
end
it 'is invalid if the last name has less than two characters' do
user = User.create(first_name: 'abc', last_name: 'a')
expect(user).to_not be_valid
end
Your tests should look like this:
it 'invalid length' do
user = User.new(first_name: '#', last_name: '#')
user.valid?
expect(user.errors.count).to eq 2
expect(user.errors[:first_name]).to include "is too short (minimum is 2 characters)"
expect(user.errors[:last_name]).to include "is too short (minimum is 2 characters)"
end
The user.valid? call will run the new user against the validations which will populate the errors.
That's a very verbose test to do a unit test - I highly recommend shoulda matchers. You can test the above in just two lines:
it { is_expected.to ensure_length_of(:first_name).is_at_least(2) }
it { is_expected.to ensure_length_of(:last_name).is_at_least(2) }
I'm trying to test validations on my Rails 4 application. The problem is that adding one specific validation all test passes, regardless if remaining validations are commented or not. Here's my code:
# test/models/company_test.rb
setup do
#company = companies(:one)
#company2 = companies(:two)
end
test "should not save company with invalid name" do
#company.name = ""
assert !#company.save, "Saved company without name"
#company2.name = #company.name
assert !#company2.save, "Saved company without unique name"
#company.name = "a"
assert !#company.save, "Saved company with shorter name than 2 characters"
#company.name = rand(36**65).to_s(36)
assert !#company.save, "Saved company with longer name than 64 characters"
end
test "should not save company if website is invalid" do
#company.website = "foo"
assert !#company.save, "Saved company with invalid website"
end
# app/models/company.rb
validates :name, :owner_id, presence: true, uniqueness: true
validates :name, length: { minimum: 2, maximum: 64 }
validates :website, :format => URI::regexp(%w(http https))
Now if I comment out name validations, all tests still passes. And then if I comment out website validation also, then all tests fail.
Didn't find what's wrong with this particular code, but got around it:
I've used regular expression from here and placed it with format validation defined in Ruby on Rails guide.
# app/models/company.rb
validates :website, format: { with: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+#)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+#)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%#.\w_]*)#?(?:[\w]*))?)/, message: "invalid url" }