Rspec be_valid fails on a valid test - ruby-on-rails

I'm learning TDD with Rails 4 and rspec. I've made some test cases for my user model to check the password lengths. I have two tests so far that checks whether a user input a password that was too short and one where the password is between 6 - 10 characters.
So far, the "password is too short" test passes:
it "validation says password too short if password is less than 6 characters" do
short_password = User.create(email: "tester#gmail.com", password: "12345")
expect(short_password).not_to be_valid
end
However, on the test where I do have a valid password, it fails:
it "validation allows passwords larger than 6 and less than 10" do
good_password = User.create(email: "tester2#gmail.com", password: "blahblah")
expect(good_password).to be_valid
end
And I get this error:
Failure/Error: expect(good_password).to be_valid
expected #<User id: 1, email: "tester2#gmail.com",
created_at: "2014-06-21 02:43:42", updated_at: "2014-06-21 02:43:42",
password_digest: nil, password: nil, password_hash: "$2a$10$7u0xdDEcc6KJcAi32LBW7uzV9n7xYbfOhZWdcOnU5Cdm...",
password_salt: "$2a$10$7u0xdDEcc6KJcAi32LBW7u"> to be valid,
but got errors: Password can't be blank, Password is too short (minimum is 6 characters)
# ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'
Here's my model code:
class User < ActiveRecord::Base
has_many :pets, dependent: :destroy
accepts_nested_attributes_for :pets, :allow_destroy => true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: true
validates :password, presence: true, :length => 6..10, :confirmation => true
#callbacks
before_save :encrypt_password
after_save :clear_password
#method to authenticate the user and password
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
#method to encrypt password
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
#clears password
def clear_password
self.password = nil
end
end
I'm confused on why the password is nil when I create the test object.

You have a password presence requirement on your model, but then you have an after_save hook that nilifies the password and puts the record into an invalid state. The first test passes because your records are always being put into an invalid state by the after_save hook. You need to rethink how you're handling password storage; once you resolve that, here are some code samples to help give you some ways to test this:
# Set up a :user factory in spec/factories.rb; it should look something like:
FactoryBot.define do
factory :user do
sequence(:email) { |n| "tester+#{n}#gmail.com" }
password { SecureRandom.hex(6) }
end
end
# In your spec:
let(:user) { create :user, password: password }
context 'password' do
context 'length < 6' do
let(:password) { '12345' }
it { expect(user).not_to be_valid }
it { user.errors.message[:password]).to include('something') }
end
context 'length >= 6' do
context 'length < 10' do
let(:password) { 'blahblah' }
it { expect(user).to be_valid }
end
context 'length >= 10' do
let(:password) { 'blahblahblah' }
it { expect(user).not_to be_valid }
end
end
end
You can also use shoulda matchers:
it { should_not allow_value('12345').for(:password) }
it { should allow_value('12345blah').for(:password) }

The most likely problem is the password field is not mass assignable. That is why password is nil in the output message.
Try this instead:
it "validation allows passwords larger than 6 and less than 10" do
good_password = User.create(email: "tester2#gmail.com")
good_password.password = "blahblah"
expect(good_password).to be_valid
end
Note that your first test is passing accidentally - it has the same problem as the second test (password isn't being assigned). This means you aren't actually testing that the password is rejected when less than 6 characters atm.
See this article on mass assignment for more details.
EDIT: Leo Correa's comment may suggest this may not be the case for you. Posting your model code would help...

Related

Testing Rails model uniqueness with duplicate entry

I have a restriction on my Rails DB to force unique usernames, and I create a user at the start of each of my model tests. I'm trying to create a second user with the same username as the first and I expect this to not be valid, but it is returning as valid.
I've tried tweaking the code to use the new, save, and create methods when generating new users but with no success.
Registration Model:
class Registration < ApplicationRecord
#username_length = (3..20)
#password_requirements = /\A
(?=.{8,}) # Password must be at least 8 characters
(?=.*\d) # Password must contain at least one number
(?=.*[a-z]) # Password must contain at least one lowercase letter
(?=.*[A-Z]) # Password must contain at least one capital letter
# Password must have special character
(?=.*[['!', '#', '#', '$', '%', '^', '&']])
/x
validates :username, length: #username_length, uniqueness: true
validates :password, format: #password_requirements
validates :email, uniqueness: true
has_secure_password
has_secure_token :auth_token
def invalidate_token
self.update_columns(auth_token: nil)
end
def self.validate_login(username, password)
user = Registration.find_by(username: username)
if user && user.authenticate(password)
user
end
end
end
Registration Tests:
require 'rails_helper'
RSpec.describe Registration, type: :model do
before do
#user = Registration.new(
username: '1234',
password: 'abcdeF7#',
email: 'test#test.com',
name: 'One'
)
end
it 'should not be valid if the username is already taken' do
#user.save!(username: '1234')
expect(#user).not_to be_valid
end
end
I would expect this test to pass due to it being a duplicate username.
As fabio said, you dont have second Registration object to check uniquness.
You just checked your saved #user is valid or not which is always valid and saved in DB. To check your uniqueness validation you can do something like this -
RSpec.describe Registration, type: :model do
before do
#user = Registration.create(
username: '1234',
password: 'abcdeF7#',
email: 'test#test.com',
name: 'One'
)
#invalid_user = #user.dup
#invalid_user.email = "test1#test.com"
end
it 'should not be valid if the username is already taken' do
expect(#invalid_user.valid?).should be_falsey
end
end
#user.save! will raise an error even before reaching the expect as mentioned in comments by Fabio
Also, if it is important to you to test db level constraint you can do:
expect { #user.save validate: false }.to raise_error(ActiveRecord::RecordNotUnique)

Rails understand regex differently then rubular

I wanted to do a simple regex-based email validation:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([\S]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || 'is not an email')
end
end
end
Which is mentioned in rails documentation.
I have checked it in rubular and everythings works fine, but in rails, emails with dot in local address fails validation for some reason (john.doe#example.com). What's weird is that it accepts any other non-whitespace characters (e.g &^%#!*() excluding # of course).
class User < ApplicationRecord
validates :email, presence: true, length: { maximum: 64 },
uniqueness: { case_sensitive: false }, email: true
validates :password, presence: true, length: { in: 8..64 }
validates :first_name, length: { in: 2..16 }, allow_blank: true
validates :last_name, length: { in: 2..32 }, allow_blank: true
validates :student_id, length: { in: 5..6 }, allow_blank: true,
numericality: true
end
And failing test:
test 'should validate email with special characters in local address' do
#user.email = 'john.doe#domain.com'
assert #user.valid?
end
Whole user_test.rb
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(email: 'johndoe#domain.com', password: 'password12',
first_name: 'John', last_name: 'Doe',
student_id: '112233')
end
# Presence validation
test 'user validate user' do
assert #user.valid?
end
test 'should not validate empty email' do
#user.email = ''
assert_not #user.valid?
end
test 'should not validate blank password' do
#user.password = ' '
assert_not #user.valid?
end
test 'should validate blank first name' do
#user.first_name = ''
assert #user.valid?
end
test 'should validate blank last name' do
#user.last_name = ''
assert #user.valid?
end
test 'should validate blank student id' do
#user.student_id = ''
assert #user.valid?
end
# Length validation
test 'should not validate too long email' do
#user.password = 'a' * 65
assert_not #user.valid?
end
test 'should not validate too long password' do
#user.password = 'a' * 65
assert_not #user.valid?
end
test 'should not validate too short password' do
#user.password = 'a' * 7
assert_not #user.valid?
end
test 'should not validate too long first name' do
#user.first_name = 'a' * 17
assert_not #user.valid?
end
test 'should not validate too short first name' do
#user.first_name = 'a'
assert_not #user.valid?
end
test 'should not validate too long last name' do
#user.last_name = 'a' * 33
assert_not #user.valid?
end
test 'should not validate too short last name' do
#user.last_name = 'a'
assert_not #user.valid?
end
test 'should not validate too long student id' do
#user.student_id = '1234567'
assert_not #user.valid?
end
test 'should not validate too short student id' do
#user.student_id = '1234'
assert_not #user.valid?
end
# Email format validation
test 'should not validate email with wrong format' do
#user.email = 'john.doe#domain'
assert_not #user.valid?
end
test 'should validate email with special characters in local address' do
#user.email = 'john.doe#domain.com'
assert #user.valid?
end
test 'should not validate email with special characters in domain' do
#user.email = 'john.doe#dom_ain.com'
assert_not #user.valid?
end
# Numeric-only validation
test 'should not validate student id with non-numeric characters' do
#user.student_id = 'ab123c'
assert_not #user.valid?
end
# Uniqueness validation
test 'should not validate duplicated email' do
#copycat_user = #user.dup
#user.save
assert_not #copycat_user.valid?
end
end
Any idea what could be the reason for that weird behaviour?
I've got the resolution.
I had fixture with same parameters as
#user = User.new(email: 'johndoe#domain.com', password: 'password12',
first_name: 'John', last_name: 'Doe',
student_id: '112233')
Since email must be unique, it gave me an error that email has already been taken.

Testing conditional validation with controller inputs in Rails

I have custom validator for password that takes a updating_password field from the controller
attr_accessor :updating_password
validates :password, presence: true, if: :should_validate_password?
validates :password, length: { minimum: 6 }, if: :should_validate_password?
def should_validate_password?
updating_password || new_record?
end
I want to stub out the updating_password field in my User model RSpec test, something like
before(:each) do
#user_valid = FactoryGirl.create(:user)
end
it "validates for password when updating_password is true" do
old_password = #user_valid.password
subject { #user_valid }
allow(subject).to receive(:updating_password).and_return(true)
#user_valid.update(password: "short", password_confirmation: "short")
expect(#user_valid.password).to eql(old_password)
end
The password should not be updated in this case because it is too short but the test is failing. Any help would be appreciated
I'd recommend not stubbing out your model validations. Instead you might test what you're trying to do like this
# spec/models/user_spec.rb
describe User do
describe 'validations' do
context 'while updating password' do
let(:user){ FactoryGirl.create(:user, updating_password: true) }
it 'requires password to be at least 6 characters long' do
expect {user.update!(password: 'short')}.to raise_error(ActiveRecord:RecordInvalid)
end
it 'requires password to be present' do
expect {user.update!(password: nil))}.to raise_error(ActiveRecord:RecordInvalid)
end
end
end
end

Password, Password_Confirmation ActiveModel::MassAssignmentSecurity::Error:

I'm working through the Rails Tutorial and I've gotten stuck. I'm trying to use a password and password_confirmation.
I'm getting the error(s):
15) User when password confirmation is nil
Failure/Error: #user = User.new(name: "Example User", email: "user#example.com", password: "foobar", password_confirmation: "foobar")
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: password, password_confirmation
# ./spec/models/user_spec.rb:5:in `new'
# ./spec/models/user_spec.rb:5:in `block (2 levels) in <top (required)>'
Finished in 0.21758 seconds
25 examples, 15 failures
Failed examples:
rspec ./spec/models/user_spec.rb:8 # User
rspec ./spec/models/user_spec.rb:9 # User
rspec ./spec/models/user_spec.rb:10 # User
rspec ./spec/models/user_spec.rb:11 # User
rspec ./spec/models/user_spec.rb:12 # User
rspec ./spec/models/user_spec.rb:14 # User
rspec ./spec/models/user_spec.rb:17 # User when name is not present
rspec ./spec/models/user_spec.rb:21 # User when name is too long
rspec ./spec/models/user_spec.rb:25 # User when email format is invalid should be invalid
rspec ./spec/models/user_spec.rb:33 # User when email format is invalid when email format is valid should be valid
rspec ./spec/models/user_spec.rb:47 # User when email address is already taken
rspec ./spec/models/user_spec.rb:55 # User when email address is already taken
rspec ./spec/models/user_spec.rb:59 # User when password is not present
rspec ./spec/models/user_spec.rb:63 # User when password doesn't match confirmation
rspec ./spec/models/user_spec.rb:67 # User when password confirmation is nil
All of the errors are for the same reason.
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :name
before_save { |user| user.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
#has_secure_password
has_many :event
end
user_spec.rb
require 'spec_helper'
describe User do
before do
#user = User.new(name: "Example User", email: "user#example.com", password: "foobar", password_confirmation: "foobar")
end
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
Any ideas would be appreciated.
Add password, :password_confirmation to attr_accessible in user.rb
attr_accessible :name, :email, :password, :password_confirmation
attr_accessible method takes list of attributes to be accessible. the other attribute will be protected see Mass Assignment for the reason.

Rails, undefined method error. But I can use this method in rails console

In my rspec testing. I got NoMethodError:
undefined method `password_digest='.
But in rails console. I do can use .password_digest method.
I am so confused. I searched google. Got sever threads about this. even in stackoverflow. But those don't help. I do have password_digest as a field in my database.
I can even set my password_digest
see:
1.9.2-p290 :005 > user.password_digest
=> "$2a$10$X8CSsstOqZKKA6qVHpW9.uH5Lzd7dxfGNCAxvIbePpcfBg8KFbD4y"
1.9.2-p290 :006 > user.password_digest = 1
=> 1
1.9.2-p290 :007 > user.password_digest
=> 1
And also, in my app. everything seems fine. That's weird.
Please help...
1.9.2-p290 :002 > User.first.password_digest
User Load (0.3ms) SELECT "users".* FROM "users" LIMIT 1
=> "$2a$10$NCfX2SgKeqGARJ68StxRJuCbbbK7g18n5FPxbHY5THwg4pAdHUvui"
1.9.2-p290 :003 >
[5]+ Stopped rails console
luke#Macbook-Pro~/Documents/workspace/RoR/rails_projects/sample_app2012$ bundle exec rspec spec/models/user_spec.rb
DEPRECATION WARNING: The InstanceMethods module inside ActiveSupport::Concern will be no longer included automatically. Please define instance methods directly in #<Class:0x000001029f6688> instead. (called from <top (required)> at /Users/luke/Documents/workspace/RoR/rails_projects/sample_app2012/spec/models/user_spec.rb:14)
DEPRECATION WARNING: The InstanceMethods module inside ActiveSupport::Concern will be no longer included automatically. Please define instance methods directly in #<Class:0x000001029f6688> instead. (called from <top (required)> at /Users/luke/Documents/workspace/RoR/rails_projects/sample_app2012/spec/models/user_spec.rb:14)
FFFFFFFFFFFFFFFFFFFFF
Failures:
1) User
Failure/Error: #user = User.new(name: "Example User", email: "user#example.com",
NoMethodError:
undefined method `password_digest=' for #<User:0x00000100beee60>
# ./spec/models/user_spec.rb:17:in `new'
# ./spec/models/user_spec.rb:17:in `block (2 levels) in <top (required)>'
..... There are 19 more failures, to save space, i didn't paste them here.
And this is my user_spec.rb code:
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
require 'spec_helper'
describe User do
before(:each) do
#user = User.new(name: "Example User", email: "user#example.com",
password: "foobar", password_confirmation: "foobar")
end
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest)}
it { should respond_to(:password)}
it { should respond_to(:password_confirmation)}
it { should respond_to(:remember_token) }
it { should respond_to(:authenticate) }
it { should be_valid }
describe "when name is not present" do
before { #user.name = " " }
it { should_not be_valid }
end
describe "when email is not present" do
before { #user.email = " " }
it { should_not be_valid }
end
describe "when name is too long" do
before { #user.name = "a" * 51 }
it { should_not be_valid }
end
describe "when email format is valid" do
valid_addresses = %w[user#foo.com THE_USER#foo.bar.org first.last#foo.jp]
valid_addresses.each do |valid_address|
before { #user.email = valid_address}
it {should be_valid }
end
end
describe "when email address is already taken" do
before do
user_with_same_email = #user.dup
user_with_same_email.email = #user.email.upcase
user_with_same_email.save
end
it { should_not be_valid }
end
describe "when password is not present" do
before { #user.password = #user.password_confirmation = " " }
it {should_not be_valid}
end
describe "when password doesn't match confirmation" do
before { #user.password_confirmation = "mismatch" }
it { should_not be_valid }
end
describe "return value of authenticate method" do
before { #user.save }
let(:found_user) { User.find_by_email(#user.email) }
describe "with valid password" do
it { should == found_user.authenticate(#user.password) }
end
describe "with invalid password" do
let(:user_for_invalid_password) { found_user.authenticate("invalid") }
it { should_not == user_for_invalid_password }
specify { user_for_invalid_password.should be_false }
end
describe "remember token" do
before { #user.save }
its(:remember_token) { should_not be_blank }
end
end
end
And this is my user.rb under modles:
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
class User < ActiveRecord::Base
has_secure_password
attr_accessible :name, :email, :password, :password_confirmation
validates :name, presence: true, :length => { maximum: 50 }
valid_email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { with: valid_email_regex },
:uniqueness => { case_sensitive: false }
validates :password, length: { minimum: 6}
end
Was just stuck on this for a while as well.
Don't forget
rake db:test:prepare after new migrations!
class User has method password_diges but has no method password_digest=
this is not the same methods, so instance of User shall not be responsible on password_digest
it shoud return password_digest but should_not respond_to password_digest i think
You can use User.first.password_digest in console because password_digest and password_digest= isn't the same methods.
password_digest= same as:
def password_digest=(digest)
#password_digest = digest
end
otherwise password_digest:
def password_digest
#password_digest
end
In your console, you're trying the password_digest method, not the password_digest= method. Are you able to execute the following in the console? User.first.password_digest = "$2a$10$NCfX2SgKeqGARJ68StxRJuCbbbK7g18n5FPxbHY5THwg4pAdHUvui"
I think your issue may be rooted in the fact that you're specifying only certain attributes in your User model as being attr_accessible. Try adding :password_digest to that list and see if your test passes.
Hi I am new to RoR and I learned RailsTutorial and encountered this issue too. After a long search in Google I found my db/test.sqlite3 hasn't been updated. which means there was 'password_digest' in my db/development.sqlite3 but there wasn't that column in my db/test.sqlite3.
And after I run the command 'rake db:reset' and 'rake db:test:prepare', the issue was gone.
I had the exactly same issue yesterday. The console in development and in test environment work well. And the web app itself works great as well. But the test (almost the same test as the question described) fail for no method error.
I've tried to stop spring, recreate test database, kill memcached. But it doesn't work.
Finally, I added some data into the fixture file test/fixtures/users.yml.
harry:
name: Harry
email: harry#example.com
password_digest: <%= User.digest('password') %>
It was empty before. Then the tests passed. I made the user.yml empty afterwards.The tests still passed.
I don't know why yet. But hope it helps.

Resources