I was looking at this answer to see how to test a session controller and wrote something like this:
require 'spec_helper'
describe SessionsController do
context "We should login to the system and create a session" do
let :credentials do
{:user_name => "MyString", :password => "someSimpleP{ass}"}
end
let :user do
FactoryGirl.create(:user, credentials)
end
before :each do
post :create , credentials
end
it "should create a session" do
puts user.inspect
puts session[:user_id]
#session[:user_id].should == user.id
end
end
end
Based on that I created a factory girl user:
FactoryGirl.define do
factory :user, :class => 'User' do
name "sample_user"
email "MyString#gmail.com"
user_name "MyString"
password "someSimpleP{ass}"
end
end
Now it all works - exceot for the before :each do statement - it never "logs" the "user" in - thus I cannot test the controllers functionality of, is a session properly created?
Now most would say, use capybara and test it through that way - but that's wrong, IMO - sure if I'm doing front end testing that would work, but I'm testing controller based logic. Can some one tell me why this isn't working? routing works fine.
My puts session[:user_id] is coming up nil, when it shouldn't
let is lazily evaluated, even for the before clause, so the user has not been created as of the time you do the post to login. If you change to using let!, you'll avoid this problem.
You misunderstood SessionsController and RegistrationsController.
A Session is for an user who has already registered, not for creating an user. #create in SessionController means to create a session, not an user.
RegistrationController is for creating user with full details including password_confirmation.
To test SessionsController, you need to create a valid user in FactoryGirl at first, then use his credentials say email and password to sign in.
Related
I've integrated Devise with my RoR app and am now trying to test my Controllers, specifically the one that routes me to my root_url.
I've used this HOWTO on Devise's page to setup my admin/user Factories, but there is an additional component that is part of my user signup process, which is creating a Company.
So:
User: has_one :company
Company: has_many :users
The flow for a new user looks like this:
User signs up
User confirms account (via email) and is redirected to the login page
User logs in
User fills out Company information and submits
User is then redirected to Pages#home (which is my root_url)
Using Devise's HOWTO, I created a ControllerHelpers file within Support:
module ControllerHelpers
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
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
end
end
I suspect there is something wrong with my User Factory since it doesn't seem like a Company is being created, but I'm so new to RSpec, I'm unsure.
FactoryGirl.define do
factory :user do
first_name "Test"
last_name "User"
full_name "Test User"
email "test#user.com"
phone_number "111-222-3333"
terms_accepted true
time_zone "Central Time (US & Canada)"
password "password"
password_confirmation "password"
confirmed_at Date.today
association :company
end
end
And I have a company.rb factory as well:
FactoryGirl.define do
factory :company do
id 1
name "ACME Test"
address_1 "123 Shady Lane."
address_2 "Suite 400"
city "Testville"
state "Test"
zip_code "12345"
has_payment_plan false
stripe_id "cus_34d434343e4e3e3"
locked false
end
end
My pages_controller_spec.rb is simple at this point:
require 'rails_helper'
RSpec.describe PagesController, :type => :controller do
describe "User: GET #home" do
login_user
it "signs in the user" do
expect(response).to render_template(:home)
end
end
end
This results in the following RSpec error:
1) PagesController User: GET #home signs in the user
Failure/Error: expect(response).to render_template(:home)
expecting <"home"> but was a redirect to <http://test.host/companies/new>
# ./spec/controllers/pages_controller_spec.rb:10:in `block (3 levels) in <top (required)>'
So, it's not even doing the render_template portion of my test?
UPDATE: Added Home Controller
controllers/pages_controller#home
def home
if current_user && current_user.company
verify_subscription
get_company_and_locations
get_network_hosts
get_network_hosts_at_risk
#network_hosts_snip = #network_hosts_at_risk.sort_by{ |h| -h.security_percentage }.first(5)
get_company_issues
#issues = #issues.sort_by{ |i| -i.cvss_score }.first(5)
#deferred_issues = #company.deferred_issues.last(5)
#deferred_hosts = #company.deferred_hosts.last(5)
else
redirect_to new_company_path
end
end
As we found out together in the chat... Your company was created but wasn't persisted to DB. It's because you had strategy: :build in your factory.
strategy: :build means that your association object will be created but won't be persisted to DB. To persist it you should use strategy: :create. Or in your use case you can replace association :company, strategy: :build with just company. FactoryGirl is smart enough to recognize it as an association which must be created and persisted.
And you need to set up FactoryGirl association between company and subscription the same way.
I'm working through Michael Hartl's Rails Tutorial. I've come to Chapter 9, Exercise 1. It asks you to add a test to verify that the admin attribute of the User class is not accessible. Here's the User class with irrelevant portions commented out:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
attr_protected :admin
# before_save methods
# validations
# private methods
end
And here's the test I'm using to validate that the admin attribute is not accessible.
describe User do
before do
#user = User.new(
name: "Example User",
email: "user#example.com",
password: "foobar123",
password_confirmation: "foobar123")
end
subject { #user }
describe "accessible attributes" do
it "should not allow access to admin" do
expect do
#user.admin = true
end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
end
The test fails. It says no errors were raised, in spite of the fact that the admin attribute is protected. How can I get the test to pass?
From the Ruby documentation:
Mass assignment security provides an interface for protecting attributes from end-user assignment.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html
Try this code instead
describe "accesible attributes" do
it "should not allow access to admin" do
expect do
User.new(admin: true)
end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
As Rails docs claim about attr_protected
Attributes named in this macro are protected from mass-assignment, such as new(attributes), update_attributes(attributes), or attributes=(attributes).
So you can change field manually. 'attr_protected' is only about mass-assignment.
This only works for mass assignments like setting the field from a form submit. Try something like this:
#user.update_attrtibutes(:admin => true)
#user.admin.should be_false
#agaved. This answer may come late and you may already have the answer but I wanted to answer your question, it may help somebody else.
The best way to understand how update_attributes differs from direct assignment
#user.admin = true is to try and do it in your console. If you are following Hartl's tutorial, try the following:
#user = User.first
#user.admin?
=> true
#user.admin = false
=> false
Direct assignment manages to change the value of the attribute admin for user from true to false without raising a Mass Assignment Error. This is because Mass Assignment Errors are raised when you call update_attributes or create a new user User.new using an attribute that is not accessible. In other words, Rails raises mass assignment errors when a user tries to update (attribute_update) or create User.new(admin: true) a new user with attributes that are not accessible to her. In the above case, direct assignment is not using the create or update methods of the user controller.
They are very similar pieces of code since you can use direct assignment to force a change in the admin attribute in the above case using #user.save!(validate: false) directly in IRB but as I said above this does not use the create or update method of your user controller and, hence, it will not throw the error.
I hope that helps, this helped me.
[Spoiler alert: If you are trying to solve the exercises in Hartl's book on your own, I'm pretty sure I'm about to give the answer away. Even though the answer that has been accepted is interesting information, I don't believe it was what Hartl had in mind as that would require knowledge the book has not covered and also does not relate it specifically to updates via web action or use the test he provides.]
I think you might be thinking this exercise is a lot harder than it actually is, if I got it right. First of all, you have misunderstood the hint:
Hint: Your first step should be to add admin to the list of permitted parameters in user_params.
It does not say to change its attr declaration in the class. It says to modify the helper function user_params. So I added it to the list in users_controller.rb:
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation, :admin)
end
Next, I copied the code in Listing 9.48 to the indicated place in spec/requests/user_pages_spec.rb:
require 'spec_helper'
describe "User pages" do
.
.
.
describe "edit" do
.
.
.
describe "forbidden attributes" do
let(:params) do
{ user: { admin: true, password: user.password,
password_confirmation: user.password } }
end
before do
sign_in user, no_capybara: true
patch user_path(user), params
end
specify { expect(user.reload).not_to be_admin }
end
end
end
The test then fails, showing that it is possible to pass in an admin parameter and thus change a normal user to an admin, which is not what you would want to allow:
$ rspec spec
.....................[edited out dots].................................F
Failures:
1) User pages edit forbidden attributes
Failure/Error: specify { expect(user.reload).not_to be_admin }
expected admin? to return false, got true
# ./spec/requests/user_pages_spec.rb:180:in `block (4 levels) in <top (required)>'
Finished in 4.15 seconds
91 examples, 1 failure
Failed examples:
rspec ./spec/requests/user_pages_spec.rb:180 # User pages edit forbidden attributes
Then, to make it impossible to pass in an admin value via a web action, I simply removed :admin from the list of acceptable user_params, undoing the first change:
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
Now the attempt to patch the user with a new admin value fails... and the test for it succeeds, verifying "that the admin attribute isn’t editable through the web."
$ rspec spec
...........................................................................................
Finished in 4.2 seconds
91 examples, 0 failures
Following the hint, I first added :admin to attr_accessible in app/models/user.rb to start with a red.
I then added the test:
describe "admin attribute" do
it "should not be accessible" do
expect do
#user.update_attributes(:admin => true)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
to the spec and got a red.
Removing :admin from user.rb I get a green. So far so good.
What puzzles me is why I should use the sintax:
#user.update_attributes(:admin => true)
instead of #user.admin = true (I checked and in this case it doesn't work).
Trying to write some tests for code I've already written, with a view to extending my code using test-driven development.
I have a controller whose index action calls a 'user_info' method, which just collects together some instance variables relying on Sorcery's current_user variable. For example:
def user_info
#current_A = current_user.a
#current_B = current_user.b
end
def index
user_info
// rest of the method goes here
end
I started writing some tests using rspec, just to get a feel for testing this code base. My controller spec is very basic and looks like this:
describe MyController do
describe "GET 'index'" do
get 'index'
response.should be_success
end
end
However, I get the following error when I try to run this spec:
NoMethodError: undefined method 'a' for false:FalseClass
First of all, how do I get my spec to recognize the Sorcery method current_user? And, out of curiosity, why is current_user being flagged as an instance of FalseClass? If it's not calling the Sorcery method, (and I haven't defined current_user anywhere else in my code), should it not appear as nil?
To use Sorcery test helpers you need the following lines in your spec_helper.rb.
The following needs to be in the Rspec.configure block:
RSpec.configure do |config|
config.include Sorcery::TestHelpers::Rails
end
After you have this in place you can use the Sorcery test helpers. For a Controller test you would add the following to your test.
#user = either a fixture or a factory to define the user
login_user
If you don't want to specify #user you can pass an argument.
login_user(fixture or factory definition)
Once you login the current_user should be available to your tests.
logout_user is also available.
See the Sorcery Wiki for information on setting up a user fixture to work with the login_user helper.
Richard, the problem is likely that you don't have a current_user.
To do that, you need to simulate the login process.
You can do that with a controller spec, but I don't have a good example here. I was writing specs on existing code, like you, and it made sense to use request specs instead.
I also don't have one for Sorcery (I should!!) and I am here using Capybara for filling in forms,. Still, here is how my spec looked:
(Here :account is the same as :user would be)
context "when logged in" do
before :each do
#account = Factory.create(:account)
#current_game = Factory(:game_stat)
visit login_path
fill_in 'Username or Email Address', :with => #account.email
fill_in 'Password', :with => #account.password
click_button('Log in')
end
So factories are another matter, mine looked like this:
Factory.define :account do |f|
f.sequence(:username) { |n| "ecj#{n}" }
f.sequence(:email) { |n| "ecj#{n}#edjones.com" }
f.password "secret"
f.password_confirmation {|u| u.password }
end
You don't have to use factories, but you do need to get that session and current_user established.
On important bit is to ensure the user is activated after creation if you're using the :user_activation submodule of Sorcery.
So, if you're using the fabrication gem, that would look like,
Fabricator(:properly_activated_user, :from => :user) do
after_create { |user| user.activate! }
end
As #nmott mentioned you need to do two things:
1) Register text helper methods using:
RSpec.configure do |config|
config.include Sorcery::TestHelpers::Rails
end
2) In your example access current_user through controller.current_user like that:
login_user(user)
expect(controller.current_user).to be_present
I'm new to RSpec, but I'm attempting to create RSpec2 controller specs for some new functionality in a Rails 3 app. I have a model that belongs_to :user and I want to ensure that the user is getting set by my controller when I create a new instance of my model.
The currently logged on user should be the user that is assigned to the model in the create action. I'm having trouble getting my specs to work. Here's what I have
# this was generated by rspec-rails and sets up my mock model
def mock_plan_order(stubs={})
(#mock_plan_order ||= mock_model(PlanOrder).as_null_object).tap do |plan_order|
plan_order.stub(stubs) unless stubs.empty?
end
end
# here's the actual test that's not working
context "user logged in" do
before(:each) do
login_user # this logs in and sets #current_user - this is working
end
describe "POST create" do
# the spec that is not working
it "sets the user id to the logged on user" do
PlanOrder.stub(:new).with({"days_per_week" => 3, "description"=>"Test", "purpose_id"=>1 }) { mock_plan_order(:save => true) }
post :create, :plan_order =>{"days_per_week" => 3, "description"=>"Test", "purpose_id"=>1 }
assigns(:plan_order).user.should equal(#current_user)
end
end
end
Here's the spec output
1) PlanOrdersController user logged in POST create with valid params sets the user id to the logged on user
Failure/Error: assigns(:plan_order).user.should equal(#current_user)
expected #<User:58754688> => #<User:0x3808680 #name="User_1">
got #<PlanOrder:58689360> => #<PlanOrder:0x37f8750 #name="PlanOrder_1010">
Anyone know what I'm doing wrong?
You stubbed the creation of the plan_order object, so no user will be set. Either don't mock/stub, add a mock to ensure that the user is being set.
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.