I want to write test case for below method. I'm new to unit testing. Please let me know the correct way to write test case for below method.
def create_new_user
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(self.password, password_salt)
user = User.new(email: self.email, username:self.username, password_hash: password_hash, password_salt: password_salt)
if user.valid?
user.save ? {is_created: true, err:''} : {is_created: false, err:'Something went wrong,please try later...'}
else
{is_created: false, err: 'Please enter all mandetory fields..'}
end
end
This may not be "an answer" per se but some comments/advice that might help point you in the right direction:
Your method looks to be returning a hash even though it's creating a new user. You should probably return the new user. If there are errors, the activerecord object will have those errors, no need to add your own errors.
Remember to test behavior. This is crucial to the change in point 1. The behavior of this method is: It returns a user record, either saved or not, that's the behavior. Test that.
You probably don't need to call user.valid?. Just call user.save
You can probably just return user.save itself, since, if it works, you'll get a user that is persisted/saved. If it doesn't you can check user.errors
Related
I have code in my Rails app that allows me to export a CSV file. It works fine unless there is a record that has a field with no value in it. In that case it fails. As an example, the specific failure I'm getting is saying something liek "No Method Error" and it specifically references "address_line_1" because there are some users with no address_line_1. That is just one example though. Really all fields should be protected against potential blanks. Here is the code:
def download_kids_csv
#csv_headers = ['First',
'Last',
'Child First',
'Child Last',
'Parent Email',
'School',
'Class',
'Address',
'City',
'State',
'Zip',
'Parent Phone']
#kid_data = []
#school = School.find(params[:school_id])
#school.classrooms.each do |classroom|
classroom.kids.includes(:users).each do |kid|
kid.users.each do |parent|
#kid_data << {
first: parent.first_name,
last: parent.last_name,
child_first: kid.first_name,
child_last: kid.last_name,
parent_email: parent.email,
school: #school.name,
class: classroom.classroom_name,
address: parent.addresses.first.address_line_1,
city: parent.addresses.first.city,
state: parent.addresses.first.state,
zip: parent.addresses.first.zip_code,
parent_phone: parent.phones.first.phone_number
}
end
end
end
respond_to do |format|
format.csv do
headers['Content-Disposition'] = "attachment; filename=\"#{#school.name.downcase.gsub(' ', '-')}-data.csv\""
headers['Content-Type'] ||= 'text/csv'
end
end
end
Ok so the problem you are get is because you are calling method on a nil value.
So for example when you do:
kid.first_name
and kid is nil you are doing this
nil.first_name
nil does not implement the first_name method so it throws an error. WHat you could do to circumvent this (its kinda ugly) is this
kid.try(:first_name)
This will prevent you form getting those method missing errors
For those long chains you can do the following
parent.try(:addresses).try(:first).try(:zip_code)
This should save you a lot of headache, but the root cause of your issue is data integrity you would not have to do all of this if you ensured that your data was not blank. I do however understand in the real world it easier said than done. I could give you a lecture about The Law of Demeter and how you should not be running across object to access their attributes, and how thats a code smell of bad organization of data, but its a spread sheet and sometimes you just need the data. Good luck!
To build off of the earlier answer, you can also utilize the so-called lonely operator &. if you're on Ruby 2.3.
An example would look something like this: kid&.first_name.
If you're not on that version of ruby yet, there's a good gem that can help you out in this situation that's a little bit more robust than .try.
Using that gem your code would look like kid.andand.first_name. It might be overkill in this case but the difference here is that it will only perform the first_name method call if kid is not nil. For your longer chains, parent.address.first.zip_code, this would mean that the function chain would exit immediately if parent was nil instead of calling all of the different attributes with try.
Is it possible to use unless or another conditional?
unless parent.addresses.first.address_line_1.blank?
address: parent.addresses.first.address_line_1,
end
or
if parent.addresses.first.address_line_1 != nil
address: parent.addresses.first.address_line_1,
else
address: nil || "address is empty"
end
I have a set-up where I have Devise being used as the main authentication handler for a Rails application and once in a while it appears to pull out an ActiveRecord Relation instead of a record, in the same way you might if you called Type.where( name: "bob" ) for example.
In order to work around this ( I haven't spent much time with Devise and I don't have a lot of spare time to spend exploring it ) I just wanted to make sure my sign in checked and corrected this, so I wrote a section like this:
def authenticate_user_with_assignment!
authenticate_user!
logger.debug("User is #{current_user.inspect}")
if ( current_user.is_a?(ActiveRecord::Relation) )
logger.debug ("It is a relation!")
current_user = current_user.first
else
logger.debug ("Not a relation.")
end
logger.debug("User is #{current_user.inspect}")
# do some other stuff
end
I get output like this:
User is #<User id: 1, email: "admin#testaddress.com" ... etc >
Not a relation.
User is nil
If I comment out the current_user = current_user.first line, I get this:
User is #<User id: 1, email: "admin#testaddress.com" ... etc >
Not a relation.
User is #<User id: 1, email: "admin#testaddress.com" ... etc >
So somehow it appears to be ignoring the logger statements in my if condition there but running the assignment. I can't figure out why this is happening- any ideas?
current_user = current_user.first
This creates a local variable, which shadows current_user method. Local variables are created as soon as parser notice them being declared, regardless whether assignment is actually executed or not, hence if the if condition is false, local variable current_user is declared and nil. You need to execute current_user= method instead:
self.current_user = current_user.first
If you have no such a method, define it.
I have a seeds.rb file and for some reason, this doesn't work:
#doesn't work
u=User.new
u['email']=h['email']
u['password']=h['password']
puts u['email']
puts u['password']
if u.save
puts "that saved"
else
puts "that did not save"
end
but this does:
#does work
User.create({:email => h['email'], :password => h['password']})
Is there any reason one works and one doesn't? From rails console, the first does work? Would there be any differences in validations? I run 'rake db:seed' so would think validations would be in effect in both.
thx
edit #1
sorry for lack of info. each one errors with "password can't be blank" but it then echoes out the password. Hmm...
Find out what's happening by looking at the validation errors:
else
puts "that did not save because " + u.errors.join(' ')
end
Edit: Now that you've added that validation errors contain 'password cant be blank' and it still fails to save, it becomes clear that User.create() does a number of things which User.save() omits.
What's missing is the generation of your password hash - hence the blank password error.
Try it this way:
u = User.new
u.email = h['email']
u.password = h['password']
puts u.inspect
if u.save!
puts "that saved"
else
puts "that did not save"
end
I believe the approach of accessing the attribute on the u object as u['password'] is steering you wrong.
Also, doing a u.inspect prints the entire state of the model and that should provide a cleaner way of seeing what's happening. Finally, adding a bang ! at the end of the save will cause it to 'fail fast' and give you the error immediately.
Here is my helper method which I want to test.
def posts_correlation(name)
if name.present?
author = User.find_by_name(name)
author.posts.count * 100 / Post.count if author
end
end
A factory for user.
factory :user do
email 'user#example.com'
password 'secret'
password_confirmation { password }
name 'Brian'
end
And finally a test which permanently fails.
test "should calculate posts count correlation" do
#author = FactoryGirl.create(:user, name: 'Jason')
#author.posts.expects(:count).returns(40)
Post.expects(:count).returns(100)
assert_equal 40, posts_correlation('Jason')
end
Like this.
UsersHelperTest:
FAIL should calculate posts count correlation (0.42s)
<40> expected but was <0>.
test/unit/helpers/users_helper_test.rb:11:in `block in <class:UsersHelperTest>'
And the whole problem is that mocha doesn't really mock the count value of author's posts, and it returns 0 instead of 40.
Are there any better ways of doing this: #author.posts.expects(:count).returns(40) ?
When your helper method runs, it's retrieving its own object reference to your author, not the #author defined in the test. If you were to puts #author.object_id and puts author.object_id in the helper method, you would see this problem.
A better way is to pass the setup data for the author in to your mocked record as opposed to setting up expectations on the test object.
It's been a while since I used FactoryGirl, but I think something like this should work:
#author = FactoryGirl.create(:user, name: 'Jason')
(1..40).each { |i| FactoryGirl.create(:post, user_id: #author.id ) }
Not terribly efficient, but should at least get the desired result in that the data will actually be attached to the record.
I'm trying to run the following unit-test:
def test_passwordchange
# check success
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
#change password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
assert #longbob.save!
#new password works
assert_equal #longbob, Usuario.autenticar("longbob", "nonbobpasswd")
#old pasword doesn't work anymore
assert_nil Usuario.autenticar("longbob", "longtest")
#change back again
#longbob.password = "longtest"
#longbob.password_confirmation = "longtest"
assert #longbob.save!
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
assert_nil Usuario.autenticar("longbob", "nonbobpasswd")
end
However, it throws error on the 1st line that contains "assert_equal" that says:
<#<Usuario ID: 1000003, login: "longbob", hashed_password: "078cf6ae2de80ed6c004c8c8576a5572e077a52c", salt: "1000", nombre: nil, apellido: nil, email: "lbob#mcbob.com", telefono: nil, tipo_usuario: nil, foto: nil, bol_activo: nil>> expected but was <nil>.
Here's my authenticate method:
def self.authenticate (login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if Usuario.encrypt(pass, u.salt)==u.hashed_password
nil
end
Also, I defined the following:
def password=(pass)
#password=pass
self.salt = Usuario.random_string(10) if !self.salt?
self.hashed_password = Usuario.encrypt(#password, self.salt)
end
So, I guess that should update the hashed_password every time I reassigned something to "password"... right?
Whats happening?
Thx.
UPDATE: I noticed that if I change:
assert_equal #longbob,
Usuario.autenticar("longbob",
"nonbobpasswd")
to
assert_equal #longbob2,
Usuario.autenticar("longbob",
"nonbobpasswd")
It passes that test, however it fails in the following line... Trowing the same error... What's up with that?
To answer after my comment, we find out that the problem is the call to save does not update the record in the database. I have suggested you to write another test to see this behaviour and be able to fix it, the test you have writed start to be too long and in fact the bug does not have anything related to the authenticate mechanism.
Here is the test I would write :
def test_change_password_save
old_hash = #longbob.hashed_password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
#longbob.save
assert_not_equal(old_hash, #longbox.reload.hashed_password)
end
If this test failed, I would suggest you to write another question in stackoverflow for this bug.
You're probably getting a validation error. Check the contents of #longbob.errors.
What happens if you split this into two separate statements? (Which is good practice anyway)
#longbob.password = #longbob.password_confirmation = "nonbobpasswd"
See, #password_confirmation= is actually a method, which might not return the value that was passed to it, depending on how the method was implemented.
Is it possible that changing the password variables doesn't update the hashed_password field in the database.
You probably need something like this in Usuario:
before_save :rehash
def rehash
self.hashed_password = ??? #replace ??? with whatever your hashing logic is
end