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
Related
I've been learning Rails 3 with Devise and, so far, seem to have it working quite well. I've got custom session & registration controllers, recaptcha is working and a signed-in user can upload an avatar via carrierwave, which is saved on S3. Pretty happy with my progress.
Now I'm writing Rspec tests. Not going so well! I have a reasonable User model test, but that's because I found it online (https://github.com/RailsApps/rails3-devise-rspec-cucumber/) and was able to add to it by following Michael Hartl's excellent "Ruby on Rails 3 Tutorial".
My real problem is controller test and integration tests, especially controller tests. Initially I thought I'd be able to convert the tests in Michael's book, and I have to a small degree, but it's slow progress and I seem to be constantly hitting my head against a brick wall - partly, I think, because I don't know Rspec and capybara so well (have made some very dumb mistakes) but also because I don't really understand Devise well enough and am wondering if Devise plays as nicely as it might with Rspec; I read somewhere that, because Devise is Rack based, it might not always work as one might expect with Rspec. Don't know if that's true or not?
I know some people will wonder why this might be necessary since Devise is a gem and therefore already tested but I've had a couple of instances where changes elsewhere have broken login or registration without me immediately realizing. I think a good set of controller & integration tests would have solved this.
If I was able to do this myself I would and I'd publish it for others but, so far, writing these tests has been extremely painful and I really need to move on to other things.
I'm sure I wouldn't be the only one who could use this. Anyone know of a such a suite of tests?
In response to Jesse's kind offer of help...
Here is my registrations_controller_spec. The comments in "should render the 'edit' page" show the sort of things I am struggling with. Also, "should create a user" has some things I've tried to test but not been able to:
require File.dirname(__FILE__) + '/../spec_helper'
describe Users::RegistrationsController do
include Devise::TestHelpers
fixtures :all
render_views
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe "POST 'create'" do
describe "failure" do
before(:each) do
#attr = { :email => "", :password => "",
:password_confirmation => "", :display_name => "" }
end
it "should not create a user" do
lambda do
post :create, :user_registration => #attr
end.should_not change(User, :count)
end
it "should render the 'new' page" do
post :create, :user_registration => #attr
response.should render_template('new')
end
end
describe "success" do
before(:each) do
#attr = { :email => "user#example.com",
:password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
response.should redirect_to(root_path)
#response.body.should have_selector('h1', :text => "Sample App")
#response.should have_css('h1', :text => "Sample App")
#flash[:success].should == "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
#response.should have_content "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
end.should change(User, :count).by(1)
end
end
end
describe "PUT 'update'" do
before(:each) do
#user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in #user
end
describe "Failure" do
before(:each) do
# The following information is valid except for display_name which is too long (max 20 characters)
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
end
it "should render the 'edit' page" do
put :update, :id => subject.current_user, :user => #attr
# HAVE PUT THE DEBUGS THAT I'D LIKE TO GET WORKING FIRST
# Would like to be able to debug and check I'm getting the error(s) I'm expecting
puts subject.current_user.errors.messages # doesn't show me the errors
# Would like to be able to debug what html is being returned:
puts page.html # only return the first line of html
# Would like to be able to determine that this test is failing for the right reasons
response.should have_content "Display name is too long (maximum is 20 characters)" # doesn't work
response.should render_template('edit')
end
end
describe "Success" do
it "should change the user's display name" do
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
put :update, :id => subject.current_user, :user => #attr
subject.current_user.reload
response.should redirect_to(root_path)
subject.current_user.display_name == #attr[:display_name]
end
end
end
describe "authentication of edit/update pages" do
describe "for non-signed-in users" do
before(:each) do
#user = FactoryGirl.create(:user)
end
describe "for non-signed-in users" do
it "should deny access to 'edit'" do
get :edit, :id => #user
response.should redirect_to(new_user_session_path)
end
it "should deny access to 'update'" do
put :update, :id => #user, :user => {}
response.should redirect_to(new_user_session_path)
end
end
end
end
end
Acording to Devise Wiki, controller tests have to be some kind of hibrid (unit + integration) tests. You have to create an instance of User (or you auth entity) in the database. Mocking Devise stuff is really hard, trust me.
Try this: How-To:-Controllers-and-Views-tests-with-Rails-3-and-rspec
With Cucumber, you can create a step that already do the login process.
Hope it helps.
OK, so a couple of things to make this work:
Here's how you should test that the body have specific text (you were missing the .body
response.body.should include "Display name is too long (maximum is 30 characters)"
You could also test that the #user has an error:
assigns[:user].errors[:display_name].should include "is too long (maximum is 30 characters)"
But, you do need to actually make the name be over 20/30 characters long, so I changed the attributes being posted to:
#attr = { :email => #user.email, :display_name => ("t" * 35), :current_password => #user.password }
https://gist.github.com/64151078628663aa7577
I have the following error: "Validation failed: Email address is already used" while trying to run feature for Devise user signing in.
I suspect the problem is in Factory generated user, which somehow being created twice with the same email address. I tried to run rake db:test:prepare in order to reset test database, unfortunately, with no results.
my user_factory.rb
Factory.define :user do |user|
user.email "user_#{rand(1000).to_s}#example.com"
user.password "password"
user.confirmed_at nil
end
Steps, which are failing with validation error are "Then I should be registered and signed in" and "And user signs in". my signing_in.feature
Feature: Signing in
In order to use the site
As a user
I want to be able to sign in
Scenario: Signing in via confirmation
Given there is an unconfirmed user
And I open the email with subject "Confirmation instructions"
And they click the first link in the email
Then I should be registered and signed in
Scenario: Signing in via form
Given there is a confirmed user
And I am on the homepage
And user signs in
Then I should see "Signed in successfully"
my user_steps.rb
def unconfirmed_user
#unconfirmed_user = Factory(:user)
end
def sign_up user
visit '/users/sign_up'
fill_in('Email', :with => "unique#unique.com")
fill_in('Password', :with => user.password)
fill_in('Password confirmation', :with => user.password)
click_button('Sign up')
end
def sign_in user
visit 'users/sign_in'
fill_in('Email', :with => user.email)
fill_in('Password', :with => user.password)
click_button('Sign in')
end
When /^I'm signing up$/ do
sign_up unconfirmed_user
end
Given /^there is an unconfirmed user$/ do
unconfirmed_user
end
Given /^there is a confirmed user$/ do
unconfirmed_user.confirm!
end
Then /^I should be registered and signed in$/ do
page.should have_content("Signed in as #{unconfirmed_user.email}")
end
Given /^user signs in$/ do
sign_in unconfirmed_user.confirm!
end
When /^I open the email with subject "([^"]*?)"$/ do |subject|
open_email(#unconfirmed_user.email, :with_subject => subject)
end
What it can be?
Thanks in advance for your help.
You should be clearing the database between tests, mostly using database_cleaner. Check the manual because configuration varies between setups. We are using this one (in env.rb):
DatabaseCleaner.strategy = :truncation
Before do
DatabaseCleaner.clean
end
Also, you should remove the rand directive in the factories file and use the goodies FactoryGirl provides:
u.sequence(:email){|n| "user_#{n}#example.com" }
Here's the test:
describe "admin attribute" do
before(:each) do
#user = User.create!(#attr)
end
it "should respond to admin" do
#user.should respond_to(:admin)
end
it "should not be an admin by default" do
#user.should_not be_admin
end
it "should be convertible to an admin" do
#user.toggle!(:admin)
#user.should be_admin
end
end
Here's the error:
1) User password encryption admin attribute should respond to admin
Failure/Error: #user = User.create!(#attr)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
# ./spec/models/user_spec.rb:128
I'm thinking the error might be somewhere in my data populator code:
require 'faker'
namespace :db do
desc "Fill database with sample data"
task :populate => :environment do
Rake::Task['db:reset'].invoke
admin = User.create!(:name => "Example User",
:email => "example#railstutorial.org",
:password => "foobar",
:password_confirmation => "foobar")
admin.toggle!(:admin)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}#railstutorial.org"
password = "password"
User.create!(:name => name,
:email => email,
:password => password,
:password_confirmation => password)
end
end
end
Please let me know if I should reproduce any more of my code.
UPDATE: Here's where #attr is defined, at the top of the user_spec.rb file:
require 'spec_helper'
describe User do
before(:each) do
#attr = {
:name => "Example User",
:email => "user#example.com",
:password => "foobar",
:password_confirmation => "foobar"
}
end
Check to be sure that there isn't a block further up your user_spec.rb that is calling User.create in a before(:each) block with the same email address. If your blocks are nested incorrectly, you'll get this error. For example, in the Rails tutorial, it's easy to accidentally nest your describe "admin attribute" inside your describe "password encryption" block, which uses the same before(:each) code.
Try checking for existing users in the before block:
before(:each) do
User.count.should == 0
#user = User.create!(#attr)
end
If that fails, then another user exists with the same email. This could be because another before block created a user with the same attributes, or that the test database was not correctly cleaned out after a failure. For the latter case, try running rake db:test:prepare, and then run the spec again.
before( :each ) is going to create a new user object from #attr. So if #attr isn't changing the values for its fields, and you have validations turned on to prevent duplicate, then on your 2nd test, the user object you created in the first test will collide with the one you are trying to create in the 2nd test.
There are other ways to go about testing your model without the database. For example, you can use test doubles to create and setup objects with exactly the data you want and then run your test to see if it behaves correctly. There is a [great book on RSpec, Cucumber and BDD] that could be a great source.
Edit: My apologies, I was confusing before(:each) with before(:all).
This does not seems to be ideal way of setting up test data. ie, using a rake task to populate the database.
A more standard unit testing and Rails practice would be to use both factory_girl or a test_fixture and transactional test fixture or database_cleaner gem.
Read a little bit about those, and they should be straight forward to use. They ensure, that each of your rspec test runs in isolation even when you run all of them together. That way, each test data for one test will not affect the other one.
Im trying to test my successfully creates a new user after login (using authlogic). Ive added a couple of new fields to the user so just want to make sure that the user is saved properly.
The problem is despite creating a valid user factory, whenever i try to grab its attributes to post to the create method, password and password confirmation are being ommitted. I presuem this is a security method that authlogic performs in the background. This results in validations failing and the test failing.
Im wondering how do i get round this problem? I could just type the attributes out by hand but that doesnt seem very dry.
context "on POST to :create" do
context "on posting a valid user" do
setup do
#user = Factory.build(:user)
post :create, :user => #user.attributes
end
should "be valid" do
assert #user.valid?
end
should_redirect_to("users sentences index page") { sentences_path() }
should "add user to the db" do
assert User.find_by_username(#user.username)
end
end
##User factory
Factory.define :user do |f|
f.username {Factory.next(:username) }
f.email { Factory.next(:email)}
f.password_confirmation "password"
f.password "password"
f.native_language {|nl| nl.association(:language)}
f.second_language {|nl| nl.association(:language)}
end
You definitely can't read the password and password_confirmation from the User object. You will need to merge in the :password and :password_confirmation to the #user.attributes hash. If you store that hash somewhere common with the rest of your factory definitions, it is not super dry, but it is better than hardcoding it into your test.
--preface: ignore if you want.
I'm new to rails, and working on a project that will require user authentication.
I found this tutorial and have been trying to go through it and understand what's happening. Of course, it's not exactly what I need as-is, so I've been modifying as I go along. The tutorial is also out of date in some areas, so of course I've had to update my code. So part of my problem is that I'm not sure if the bug is in my modifications, or some function that's been deprecated, or what.
--the question
This is the (simplest) test that fails. (" expected to not be nil" on the first assert statement.)
def test_authentication
#check we can log in
post :login, :user => { :username => "bob", :password => "test" }
assert_not_nil session[:user_id]
assert_equal users(:bob).id, session[:user_id]
assert_response :redirect
assert_redirected_to :action => 'welcome'
end
It calls the user_controller action login:
def login
if request.post?
if session[:user_id] = User.authenticate(params[:user][:username], params[:user][:password])
flash[:message] = "Login succeeded!"
redirect_to_stored
else
flash[:warning] = "Login failed."
end
end
end
which calls the User method authenticate. I know that authenticate works properly, however, because I have a single test that does pass:
def test_registration
#check that we can register and are logged in automatically
post :register, :user => { :username => "newuser", :password => "pass", :password_confirmation => "pass", :email => "newuser#web.com" }
assert_response :redirect
assert_not_nil session[:user_id]
assert_redirected_to :action => 'welcome'
end
which calls the user_controller action register
def register
#user = User.new(params[:user])
if request.post?
if #user.save
session[:user_id] = User.authenticate(#user.username, #user.password)
flash[:message] = "Registration succeeded"
redirect_to :action => 'welcome'
end
else
flash[:warning] = "Registration failed"
end
end
which successfully calls authenticate.
the users fixture has one relevant record:
bob:
username: bob
email: bob#mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test
salt: 1000
I've tested the hashed password and salt - "test" is the correct password.
So by my analysis, the bug has got to be in one of 3 places:
how I'm sending my post request,
how I'm accessing the parameters in the login action,
or some aspect of the fixture loading.
(originally I was using the tutorial's code to load the fixture explicitly (self.use_instantiated_fixtures = true; fixtures :users), but I read that all fixtures are automatically loaded before testing, so I took it out. That didn't change a thing.)
Of course, since I can't seem to find the problem in those areas, it could just as well be anywhere else.
Is it possible that there's a filter that is preventing your action getting called? If there's a general :before_filter => 'login_required' then you might not be reaching your login functionality at all. (Though admittedly the register action would have to be excluded for that test to pass)
In cases like this it's useful to stick some logging in (or run through a debugger) to see whether you even get to the part of the method that you think is failing. If it were me I'd stick a logger.debug("...") as the first line of the login method and then another after the check for request.post? and then another after the authentication check.