I am testing a simple password reset action and would like RSpec's "change" matcher for lambdas. But it doesn't work for this controller action. Everything works fine without that matcher. Here is the spec:
describe "#update" do
it "Updates the password and resets the token" do
#user = Factory :user
getter = lambda{
get :edit, :id => #user.perishable_token, :user => {:password_confirmation => "new_password",
:password => "new_password"}
#user.reload
}
getter.should change(#user, :password)
getter.should change(#user, :perishable_token)
end
it "Updates the password and resets the token" do
#user = Factory :user
old_password = #user.password
old_token = #user.perishable_token
get :edit, :id => #user.perishable_token, :user => {:password_confirmation => "new_password",
:password => "new_password"}
#user.reload.password.should != old_password
#user.perishable_token.should != old_token
end
end
The second it-block works, the first one doesn't. I tried to print the values inside the lambda and they indeed aren't changed.
Thank you very much for any ideas on this issue!
You need to use call to actually execute the lambda in your first example. You assign the lambda to getter, but never do a getter.call to actually execute the lambda and get your result.
So as it turns out the Change-matcher calls the proc. So that wasn't the problem. However, I was calling Edit and not Update. So nothing was supposed to change. In addition Password only exists on User objects where the password was just set and that where not retrieved from the DB since the clear text password is not saved. Here is the now working code:
describe "#update" do
it "Updates the password and resets the token" do
#user = Factory.create :user
getter = lambda{
post :update, :id => #user.perishable_token, :user => {:password_confirmation => "new_password",
:password => "new_password"}
#user.reload
}
getter.should change(#user, :crypted_password)
getter.should change(#user, :perishable_token)
end
end
I thought it would turn out to be a silly error, but two in one...
Related
I'm new to rspec testing. I need to write test code to create a new record and to check the count is 1.
I'm integrating rails with ios app.
In my registration_controller
#name = User.find_by_name(params[:user][:name]])
#params[:user][:name]] value from ios app
if !#name.nil?
user = User.new("email" => params[:user][:email])
end
In my registration_controller_spec
expect {
post :create, {:user => { 'email' => 'xxx#yyy.com'}}
}.to change(User, :count).by(1)
The if condition in controller is not allowing me to pass the test.
Failure/Error: expect {
count should have been changed by 1, but was changed by 0
If I assign any sample value to #name in controller, the test is passing. Please let me know, am I going in the right path? If not, suggest any other alternatives.
Couple of things:
Your test doesn't provide a value for params[:user][:name], so #name is nil, thus the line with User.new is never reached.
You never actually save the user that you instantiate, meaning User.count never changes.
The #name variable isn't really a name, but a User instance (if found).
You probably want to use User.find_or_create_by as Yevgeniy mentions. That means doing something like:
user = User.find_or_create_by(:name => params[:user][:name]) do |user|
user.email = params[:user][:email]
end
And for that to actually work you probably want your test to read
expect {
post :create, {:user => {'name' => 'Mr. xxx', 'email' => 'xxx#yyy.com'}}
}.to change(User, :count).by(1)
unless #name
user = User.new("email" => params[:user][:email])
end
I've got a problem with testing a create method of a controller. My test:
describe "POST #create" do
it "creates a new admin_user" do
expect{
post :create, :admin_user => FactoryGirl.attributes_for(:admin_user)
}.to change(Admin::User,:count).by(1)
end
end
And the failed spec I'm getting:
1) Admin::UsersController logged in POST #create creates a new admin_user
Failure/Error: expect{
count should have been changed by 1, but was changed by 0
# ./spec/controllers/admin_users_controller_spec.rb:75:in `block (4 levels) in <top (required)>'
Here's my controller:
def create
#admin_user = Admin::User.new(params[:admin_user])
if #admin_user.save
render nothing: true
end
end
and a factory:
require "factory_girl"
require 'ffaker'
FactoryGirl.define do
factory :admin_user, class: "Admin::User" do |f|
f.name {Faker::Name.first_name}
f.email {Faker::Internet.email}
f.password "aaa"
end
end
and my model:
class Admin::User < ActiveRecord::Base
has_secure_password
validates :email, :uniqueness => true, :presence => true
validates_presence_of :password, :on => :create
end
I have no idea what might be wrong. I've searched the whole internet for it but didn't find the answer. Adding user from the rails console works just fine. Any help would be highly appreciated. Thanks in advance.
This line:
post :create, FactoryGirl.attributes_for(:admin_user)
Should be this:
post :create, :admin_user => FactoryGirl.attributes_for(:admin_user)
As an exercise, print out the params (p params or puts params.inspect) in your create action in your controller and you'll see the difference.
EDIT You should try 2 things:
Still print out the param to see if they make sense to you.
The problem might be that your params just aren't valid. Instead of using save, try using save! which will throw an error if any of your validations are wrong, and you'll see the error in your test output. You should handle the situation if the save fails anyways.
I'm trying to test a case in our Ruby on Rails system where we lock a user out after x failed login attempts. The issue I'm having is trying to create a user has reached the number that 'locks' his account. I am using Factories to create a user like so-
Factory.define :locked_user, :class => User do |user|
user.name "Test Lock"
user.email "lock#lock.com"
user.password "blah1234"
user.password_confirmation "blah1234"
user.login_count 5
end
Where 5 is the 'magic number'. When I try to use something like
#user = Factory(:locked_user)
It creates a user in the database- but newly created users always have login_count set to zero, so it just logs him in the test. When I try the .build method like so
#user = Factory.build(:locked_user)
It sets a user with login_count = 5 like I want, but then doesn't see the user as valid and won't try to log them in (ie, it gives us the 'bad user/password' error rather then 'right user/password but you are locked out' error). I guess I'm missing something here to get RSpec to pick up the fact that this is valid user but the account should be locked. Can someone help set me straight? Below is the entire desribe block-
describe "with locked account" do
before(:each) do
#user = Factory.build(:locked_user)
#attr = { :email => #user.email, :password => #user.password}
end
it "should not allow signin with locked account" do
post :create, :session => #attr
flash.now[:error].should =~ /Invalid user locked out/i
end
end
I would recommend you either set the login_count after creating the user, or stub the method that tells you if a user login is locked.
For instance, use update_attribute to force the login_count after the user has been created:
before(:each) do
#user = Factory(:user)
#user.update_attribute(:login_count, 5)
#attr = { :email => #user.email, :password => #user.password}
end
Or use stubs to stub out the locked_login?, or equivalent method:
before(:each) do
#user = Factory(:user)
#user.stub(:locked_login?).and_return(true)
#attr = { :email => #user.email, :password => #user.password}
end
I have a controller spec and I get following failed expectation:
Failure/Error: put :update, :id => login_user.id, :user => valid_attributes
#<User:0xbd030bc> received :update_attributes with unexpected arguments
expected: ({:name=>"changed name", :email=>"changed#mail.com", :password=>"secret", :password_confirmation=>"secret"})
got: ({"name"=>"Test user", "email"=>"user#test.com", "password"=>"secret", "password_confirmation"=>"secret"})
And for me it looks like I am passing in "name" => "Test User" and I am expecting :name => "test user"
my spec looks like this:
describe 'with valid parameters' do
it 'updates the user' do
login_user = User.create!(valid_attributes)
controller.stub(:current_user).and_return(login_user)
User.any_instance.
should_receive(:update_attributes).
with(valid_attributes.merge(:email => "changed#mail.com",:name=>"changed name"))
put :update, :id => login_user.id, :user => valid_attributes
end
end
and I have something like this for my valid attributes:
def valid_attributes
{
:name => "Test user",
:email=> "user#test.com",
:password => "secret",
:password_confirmation => "secret"
}
end
so what is wrong with my parameters any suggestions?
I am using Rails 3.0.5 with rspec 2.6.0...
The failure message is telling you exactly what's going on: any instance of User is expecting update_attributes with a hash including :email => "changed#mail.com", but it's getting :email => "user#test.com" because that's what's in valid_attributes. Similarly, it's expecting :name => "changed_name", but gets :name => "Test user" because that's what's in valid_attributes.
You can simplify this example and avoid this confusion. There is no need to use valid_attributes here because should_receive intercepts the update_attributes call anyhow. I usually do this like so:
controller.stub(:current_user).and_return(mock_model(User)) # no need for a real user here
User.any_instance.
should_receive(:update_attributes).
with({"these" => "params"})
put :update, :id => login_user.id, :user => {"these" => "params"}
This way the expected and actual values are right in the example and it makes clear that it doesn't really matter what they are: whatever hash is passed in as :user is passed directly to update_attributes.
Make sense?
I set up a User AR model which has conditional validation which is pretty much identical to the Railscast episode on conditional validation. So basically my User model looks like this:
class User < ActiveRecord::Base
attr_accessor :password, :updating_password
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:if => :should_validate_password?
def should_validate_password?
updating_password || new_record?
end
end
Now in my action where the User can change their password I have the following two lines:
#user.updating_password = true
if #user.update_attributes(params[:user]) ...
so that I flag the validations to be run on the password. In development mode this works great - if the user tries to put in a password that is too short or too long the model does not pass validation. My problem is that for the life of me I can not get my tests for this to pass. Here is my spec:
require 'spec_helper'
describe PasswordsController do
render_views
before(:each) do
#user = Factory(:user)
end
describe "PUT 'update'" do
describe "validations" do
before(:each) do
test_sign_in(#user)
end
it "should reject short passwords" do
short = "short"
old_password = #user.password
#attr2 = { :password => short, :password_confirmation => short }
put :update, :user_id => #user, :old_password => #user.password, :user => #attr2
#user.password.should == old_password
end
it "should reject long passwords" do
long = "a" * 41
old_password = #user.password
#attr2 = { :password => long, :password_confirmation => long }
put :update, :user_id => #user, :old_password => #user.password, :user => #attr2
#user.password.should == old_password
end
end
end
end
When I run these tests I always get the error:
1) PasswordsController PUT 'update' validations should reject short passwords
Failure/Error: #user.password.should == old_password2
expected: "foobar"
got: "short" (using ==)
and of course the error for the password being too long. But should'nt the password be validated as a result of me setting #user.updating_password = true before any save attempts in the controller?
I think the problem isn't the code but what you expect it to do. When you call update_attributes and pass in a bad value, the value is saved into the model object even though the validation fails; the bad value has not been pushed to the database.
I think this makes sense because when the validation fails normally you would show the form again with the error messages and the inputs populated with the bad values that were passed in. In a Rails app, those values usually come from the model object in question. If the bad values weren't saved to the model they would be lost and your form would indicate that the old 'good' values had failed validation.
Instead of performing this check:
#user.password.should == old_password
Maybe try:
#user.errors[:password].should_not == nil
or some other test that makes sense.