require 'spec_helper'
describe UsersController do
let(:user){ double(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
before do
mock_model("User")
end
describe 'PATCH #update' do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
codebase:
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, flash: { success: 'succesfully updated user' }
else
flash.now[:error] = "could not update user"
render 'edit'
end
end
While the above spec passes (and passes in just 0.05 seconds!) am I doing it correctly? With the mocks above the request, and the 'normal' expectations below it? It seems a bit clumsy. Not only is it hard to read, but if one expectation fails, all of them will appear to fail.
What makes me think I'm doing it wrong is a weird error I'm getting. See the second line of the describe block, where I'm saying the instance of user (user) should have its update_attributes triggered with updated attributes? Note the and_return true method I've commented out. When it's chained on, running the above spec hits me with this stinker:
1) UsersController should_receive
Failure/Error: patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
NoMethodError:
undefined method `model_name' for RSpec::Mocks::Mock:Class
While this a over my head, I think it's because the user 'instance' isn't actually an instance, just a hash. I thought because I used mock_model and the double inherits from that mocked model, all of the active-record-y stuff such as the 'model_name' method would be set.
Anyway, how should I write this spec properly? I don't want to use FactoryGirl, I want to keep it all contained so my specs are fast and accurately report a fault.
Yes, in this case of controller testing, your basic flow is "normal", except that your innermost code needs to be within an it block (and perhaps is and is just not transcribed properly).
However, you're not making use of the mock_model call, since you're not doing anything with the result. This method doesn't make any fundamental changes to the class whose string name you pass it, it simply creates a mock object that simulates an ActiveRecord instance, which in your case you effectively discard. Like all RSpec doubles, the first parameter is just giving it a name that can be used for error messages and such.
So yes, the reason you're getting the error when you return true is that redirect_to expects #user to be an ActiveRecord instance and while it's not just a Hash as you suggest it might be, it does not have the model_name method it needs.
There are lots of ways to rewrite this, particularly if you want to support both the success and failure cases, but one way that minimizes the changes is (not fully tested):
describe UsersController do
let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
describe 'PATCH #update' do
it "should fail in this case" do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
end
Related
Can someone please try to help me out in this error? I don't really understand what the error means to me.
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: {user: {name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password"}}
end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
#Try to log in before actication.
log_in_as(user)
assert_not is_logged_in?
#Invalid activation token with valid email
get edit_account_activation_path('invalid token',email: user.email)
assert_not is_logged_in?
#Valid activation token with invalid email
get edit_account_activation_path(user.activation_token, email: 'wrong')
assert_not is_logged_in?
#Valid activation token with valid email
get edit_account_activation_path(user.activation_token,email: user.email)
assert user.reload.activated?
follow_redirect!
assert_template 'users/show'
assert is_logged_in?
end
This test is keep failing because of this error:
Failure:
UsersSignupTest#test_valid_signup_information_with_account_activation [D:/RubyOnRails/RailsProjects/sample_app/test/integration/users_signup_test.rb:53]:
Expected true to be nil or false
The Error on line 53 is coming from here.
Line 52 log_in_as(user)
Line 53 assert_not is_logged_in?
This is my methods
def is_logged_in?
!session[:user_id].nil?
end
def log_in_as(user)
session[:user_id] = user.id
end
Let's take a look at what each step of your test is actually doing, to understand the error.
First, you "sign up" a user - most likely at the UsersController#create action (or whatever else your routes.rb file defines):
post users_path, params: {user: {name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password"}}
Doing this creates a new User record, where user.disabled == true:
assert_difference 'User.count', 1
assert_not user.activated?
Next, you wanted to test that a disabled user cannot log in. The logic that dictates this is will be within the login controller action. My guess is that it will be in SessionsController#create, if such a thing exists in your application. It could also be placed somewhere more generic, like as a before_action in the ApplicationController.
In order to test this, you need to simulate a real sign-in action. I cannot say for sure what code this will require without seeing the rest of your application, but for example it could be something like:
post sessions_path, params: {user: {email: "user#example.com",
password: "password"}}
Or if you want to make this a true integration test, you could even simulate filling in the form and clicking the "log in" button. What you have here currently is more akin to a controller test, not an integration test.
The method you were using previously:
def log_in_as(user)
session[:user_id] = user.id
end
Is fine to use in tests that don't care about simulating a "real" login. But for tests that do care about the login being "real", you need to it the relevant controller action.
In this case, your logic for "not letting disabled users log in" was never actually being invoked; hence the failure.
If you want to make the error message clearer when using assertions like this, you can include the error message like so:
assert_not is_logged_in?, 'Disabled user was able to log in'
Below is a complete example of what the tests could look like. You may need to tweak the implementation slightly, depending on how your controllers are defined.
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: {user: {name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password"}}
end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
end
test "cannot log in with disabled account" do
user = user.create!(name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password")
post login_path, params: { session: { email: "user#example.com",
password: "password" } }
assert_not is_logged_in?, 'Disabled user was able to log in'
end
test "cannot login with invalid activation token" do
# ...
end
test "can login with valid activation token" do
# ...
end
The SessionController spec for an app I'm working on currently looks like this:
require 'rails_helper'
RSpec.describe SessionController do
let(:user) { create(:user, phone_verified: true, email_verified: true) }
describe "POST #create" do
context "with valid username and password" do
before do
post :create, user: { username: user.username, password: user.password }
end
specify { expect(response).to redirect_to(dashboard_path) }
specify { expect(session[:user_id]).to eq(user.id) }
end
context "with invalid username" do
before do
post :create, user: { username: "doesntexist", password: user.password }
end
specify { expect(response).to render_template(“login”) }
specify { expect(session[:user_id]).to be_nil }
end
context "with invalid password" do
before do
post :create, user: { username: user.username, password: "badpassword" }
end
specify { expect(response).to render_template(“login”) }
specify { expect(session[:user_id]).to be_nil }
end
end
end
The first two context blocks work exactly as expected. The examples in the third context block fail:
2) SessionController POST #create with invalid password
Failure/Error: post :create, user: { username: user.username, password: "badpassword" }
BCrypt::Errors::InvalidHash:
invalid hash
From what I've been able to find out on StackOverflow and by looking at the bcrypt-ruby source code, this means the password digest stored in the database is invalid. Since all three blocks are using the same let block to create the user, I'm at a loss as to why this would happen with an incorrect password and not a correct one. It also works normally when I start up rails s and try to log in through the view. Does anyone have any idea what is happening?
We're using the built-in has_secure_password method, and we're not using devise or any other authentication-related gems.
Resetting the database fixed this problem, although it's still not clear to me what caused it or why it only happened under such specific circumstances.
We have a custom SessionsController that inherits from the standard Devise::SessionsController and have lockable enabled for the User model. This works when testing manually, but in our controller tests failed_attempts is not incrementing beyond 1. If I reduce maximum_attempts attempts to 1 it will successfully lock the account in testing, but it still will not increment failed_attempts beyond 1.
Below is my test example. Any ideas as to why failed_attempts is not incrementing beyond one 1 controller tests?
it{
bad_user = create(:user, password: 'passworD1')
3.times do
post :create, user: { email: bad_user.email, password: 'asdf' }
end
post :create, user: { email: bad_user.email, password: 'asdf' }
bad_user.reload
expect(bad_user.failed_attempts).to eq(4)
expect(bad_user.locked_at).not_to be_blank
}
I tried this method warden.clear_strategies_cache! after post and I was able to lock the account.
For your example it would look like this:
it{
bad_user = create(:user, password: 'passworD1')
3.times do
post :create, user: { email: bad_user.email, password: 'asdf' }
warden.clear_strategies_cache!
end
post :create, user: { email: bad_user.email, password: 'asdf' }
bad_user.reload
expect(bad_user.failed_attempts).to eq(4)
expect(bad_user.locked_at).not_to be_blank
}
Regards,
Ruslan
Per Devise lockable module
There is a method lock_access! which locks access. That's one way to test another - brute force. Enter right email and wrong password at new_user_session_path as many time as needed per devise initializer then test new_user_unlock_path.
Using Ruby on Rails 4.2.0.rc2 I added an 'Accept terms of service' checkbox to user registration
In the user model I added
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, acceptance: true
In the view
<%= f.check_box :terms_of_service %>
and finally in the controller I added it to the list of parameters
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, :terms_of_service)
end
This works as expected but since I made a change to the implementation I expected the related tests to be in the red. However, this test passes and I don't understand why:
assert_difference 'User.count', 1 do
post users_path, user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password" }
end
I can re-write my tests like so
test "accept terms of service" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password",
terms_of_service: "0" }
end
assert_difference 'User.count', 1 do
post users_path, user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password",
terms_of_service: "1" }
end
end
but I am curious as to why the original test fails to fail. What I've taken away from this is that validates_acceptance_of passes for nil.
Is this the intended behaviour?
In a nutshell, yes, nil is allowed. I've had the same issue before.
active_model/validations/acceptance.rb
module ActiveModel
module Validations
class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
super({ allow_nil: true, accept: "1" }.merge!(options))
setup!(options[:class])
end
# ...
end
# ...
end
# ...
end
In the initializer, it merges allow_nil with the options, so yes, nil (or the lack of a value, I should say) is allowed for that. They mention it in the Rails Guide for acceptance, but I missed it.
This bit me a few times in my tests also - I kept getting passing validations when I was certain they should not pass. Now we know why!
I'm relatively new to programming, Rails, Ruby, Rspec, and the like, so thanks for your help!
My specs were very repetitive, so I wrote some spec helper methods. I can't figure out how to properly use them in my specs. Specifically, I have a users controller with create:
def create
#user = User.new(params[:user])
if #user.save
redirect_to user_path(#user)
else
render :action => :new
end
end
A bit in the spec helper that creates a valid user:
def valid_user_eilif
#test_image = Rails.root + "spec/fixtures/images/seagull.jpg"
#file = Rack::Test::UploadedFile.new(#test_image, "image/jpeg")
user = User.create!(:username => "eilif", :email => "eilif#email.org",
:image => #file, :bio => "Lots of text that I don't want to write",
:signature_quote => "Yet more text.")
user.save!
user
end
And then in my user controller spec:
before (:each) do
post :create, :user => valid_user_eilif
end
it 'should assign user to #user' do
assigns(:user).should eq(User.last)
end
When I run the spec I get the error:
Failure/Error: assigns(:user).should eq(User.last)
expected #<User id: 1, username: "eilif", email: "eilif#email.org", bio: "Lots of text that I don't want to write", signature_quote: "I feel empty.", image_file_name: "seagull.jpg", image_content_type: "image/jpeg", image_file_size: 10475, image_updated_at: "2011-05-10 23:35:55", created_at: "2011-05-10 23:35:56", updated_at: "2011-05-10 23:35:56">
got #<User id: nil, username: nil, email: nil, bio: nil, signature_quote: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, created_at: nil, updated_at: nil>
So, I assume I'm incorrectly posting to create, since nothing is created? What's the proper way to do this?
Ideally controller specs shouldn't depend on the model being able to create a row in the database. With such a simple action you can mock out the dependencies:
describe UsersController do
context "on success" do
before(:each) do
#user = mock_model(User,:save=>true)
User.stub(:new) {#user}
post :create, :user => {}
end
it "redirects" do
response.should redirect_to(user_path(#user))
end
it "assigns" do
assigns[:user].should == #user
end
end
context "on failure" do
it "renders 'new'" do
#user = mock_model(User,:save=>false)
User.stub(:new) {#user}
post :create, :user => {}
response.should render_template "users/new"
end
end
end
Notice that the specs don't pass anything in params[:user]. This helps enforce the MVC separation of concerns, whereby the model is responsible for handling the attributes, ie. validating, setting up associations, etc. You can't always keep controllers this 'skinny', but it's a good idea to try.
It looks like the problem is that #user doesn't get refreshed after the save. Try assigns(:user).reload.should eql(User.last).
But there's another slight problem, and that's probably still going to fail. You shouldn't be calling post with :user => valid_user_eilif; you want the attributes from your user record, not the actual user object itself. And you're essentially creating a new user in valid_user_eilif and then making your controller create that object again -- if you have any kind of unique constraints, you're going to get a conflict.
This is a good place to use something like factory_girl and mocks. For an example, take a look at how one of my projects handles controller specs. This example uses factory_girl, Mocha and shoulda. I'll annotate it with comments below:
describe MembersController, "POST create" do
before do
# Factory Girl - builds a record but doesn't save it
#resource = Factory.build(:member)
# Mocha expectation - overrides the default "new" behavior and makes it
# return our resource from above
Member.expects(:new).with({}).returns(#resource)
# Note how we expect it to be called with an empty hash; that's from the
# `:member` parameter to `post` below.
end
context "success" do
before do
post :create, :member => {}
end
# shoulda matchers - check for a flash message and a redirect
it { should set_the_flash.to(/successfully created/) }
it { should redirect_to(member_path(#resource)) }
end
context "failure" do
before do
# Mocha - To test a failing example in the controller, we override the
# default `save` behavior and make it return false, otherwise it would
# be true
#resource.expects(:save).returns(false)
post :create, :member => {}
end
# shoulda matchers - check for no flash message and re-render the form
it { should_not set_the_flash }
it { should render_template(:new) }
end
end