I am new to programming, this is my first application.
While creating an application in Rails, i have two models. User&list,nested.
resources :users do
resources :lists
end
These are the following routes i obtain with this setting:
user_lists GET /users/:user_id/lists(.:format) lists#index
POST /users/:user_id/lists(.:format) lists#create
new_user_list GET /users/:user_id/lists/new(.:format) lists#new
edit_user_list GET /users/:user_id/lists/:id/edit(.:format)lists#edit
user_list GET /users/:user_id/lists/:id(.:format) lists#show
PUT /users/:user_id/lists/:id(.:format) lists#update
DELETE /users/:user_id/lists/:id(.:format) lists#destroy
With regards i have created the views with the following links.
<div class="stats">
<a href="<%= user_lists_path(current_user) %>">
<%= pluralize(current_user.lists.count, 'List') %>
</a>
</div>
<div class="list">
<%= link_to 'Create List', new_user_list_path(current_user) %>
</div>
These work as expected, however when i use the same url helpers in testing i get an error.
describe "List create page" do
let(:user) { FactoryGirl.create(:user) }
before do
user.save
visit new_user_list_path(user)
end
it { should have_selector('title', text: 'SocialTask | List') }
it { should have_selector('h1', text: 'Create list') }
describe "invalid list creation" do
before { click_button 'Create list' }
it { should have_content('Error in creating list') }
end
end
This causes the tests to have an error.
Failure/Error: visit new_user_list_path(user)
NoMethodError:
undefined method `lists' for nil:NilClass
I have tried playing around with the url that did not work.
I tried updating rspec/capybara that did not work either.
I have also checked the inclusion of
config.include Rails.application.routes.url_helpers
in the spec helper.
How do i get the helpers to work? Or am i missing some minor detail?
Thanks in advance.
Helper Methods.
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def signed_in?
!current_user.nil?
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user?(user)
current_user == user
end
end
The rspec helper to sign in.
support/utilities.rb
include ApplicationHelper
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
cookies[:remember_token] = user.remember_token
end
Without seeing the stack trace, I think your problem is in the view on this line:
<%= pluralize(current_user.lists.count, 'List') %>
It seems like current_user is nil. Normally you should define some kind of helper method in your RSpec suite to simulate a user logging in. That way, current_user will return the user that you stub out in the test.
Here's an example:
# spec/support/session_helper.rb
module SessionHelper
def login(username = 'admin')
request.session[:user_id] = User.find_by_username(username).id
end
end
Yours will differ depending on how you authenticate your users. For example, Devise publishes its own set of test helpers, so you can simply include its helpers directly:
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Seems it's getting an error because the user doesn't exists. Try to change user.save to user.save! then you'll catch the error on creation I think..
Related
I have a helper method that returns one link if user_signed_in?. I'm using Devise 2.2.8, Rails 3.2.2 and Rspec 3.4.0.
application_helper.rb
def session_button
if user_signed_in?
link_to "Restricted Area", user_index_path
else
link_to "Login", new_user_session_path
end
end
And my test:
application_helper_spec.rb
describe "#session_button" do
context "user signed in" do
login_user
it "returns a link to Restricted Area" do
expect(session_button).to include "Área Restrita"
expect(session_button).to include user_index_path
end
end
context "user not signed in" do
it "returns a link to New Session" do
expect(session_button).to include "Login"
expect(session_button).to include new_user_session_path
end
end
end
login_user is a macro included in my tests. I also loaded TestHelpers.
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :helper
config.extend ControllerMacros, :type => :helper
end
The problem: I'm getting this error:
1) ApplicationHelper#session_button user signed in returns a link to Restricted Area
Failure/Error: if user_signed_in?
NoMethodError:
undefined method `user_signed_in?' for #<RSpec::ExampleGroups::ApplicationHelper::SessionButton::UserSignedIn:0x0000000795a608>
I also tried to stub the method
allow(helper).to receive(:user_signed_in?).and_return(true)
but I get the same error.
How do I stub this devise helper?
I found a way using anonymous helper but I don't know if there is a better way...
RSpec.describe ApplicationHelper, :type => :helper do
helper do
def user_signed_in?
user.present?
end
end
describe "#session_button" do
context "user signed in" do
let(:user) { FactoryGirl.create :user }
it "returns a link to Restricted Area" do
expect(session_button).to include "Área Restrita"
expect(session_button).to include user_index_path
end
end
context "user not signed in" do
let(:user) { nil }
it "returns a link to New Session" do
expect(session_button).to include "Login"
expect(session_button).to include new_user_session_path
end
end
end
end
I have following RSpec test:
require 'rails_helper'
require 'spec_helper'
RSpec.describe "Users", type: :request do
describe "sign in/out" do
describe "success" do
it "should sign a user in and out" do
attr = {:name=>"Test1",
:email => "dmishra#test.org",
:password => "foobar",
:password_confirmation => "foobar"
}
user = User.create(attr)
visit signin_path
fill_in "Email", :with => user.email
fill_in "Password", :with => user.password
puts page.body
click_button "Sign in"
controller.should be_signed_in
click_link "Sign out"
controller.should_not be_signed_in
end
end
end
end
I am getting the following error:
Failure/Error: controller.should be_signed_in
expected to respond to `signed_in?
This is because controller is nil. What is wrong here which causes controller to be nil?
Controller class is:
class SessionsController < ApplicationController
def new
#title = "Sign in"
end
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
redirect_to root_path
end
end
signed_in method is defined in session helper which is included.
Ruby platform information:
Ruby: 2.0.0p643
Rails: 4.2.1
RSpec: 3.2.2
This is a request spec (which is basically a rails integration test) which is designed to span multiple requests, possibly across controllers.
The controller variable is set by the request methods that integration testing provides (get, put, post etc.)
If instead you use the capybara DSL (visit, click etc.) then the integration test methods never get called and accordingly controller will be nil. When using capybara you don't have access to individual controller instances so you can't test things such as what signed_in? returns - you have to test a higher level behaviour (e.g. what is on the page).
I have a controller based on MHartl's RoR4 Tutorial
And just like MHartl, I'm not using Devise, I rolled my own authentication system
Having trouble with the RSpec for UsersController#Edit since the view has a call to current_user.admin? and the controller calls #path_switch = path_switch
I keep getting RSpec errors along the lines of:
1) User Pages Edit
Failure/Error: visit edit_user_path(user)
NoMethodError:
undefined method `admin?' for nil:NilClass
# ./app/controllers/users_controller.rb:106:in `path_switch'
# ./app/controllers/users_controller.rb:53:in `edit'
# ./spec/requests/user_pages_spec.rb:54:in `block (3 levels) in <top (required)>'
UsersController:
class UsersController < ApplicationController
...
def edit
#user = User.find(params[:id])
#path_switch ||= path_switch #error
end
...
def path_switch
if current_user.admin? #error
users_path
else
root_path
end
end
end
I found this really helpful article that gives me hope that I'm on the right track, but I can't get it to work.
Here's as far as I've gotten (updated):
user_pages_spec.rb:
require 'spec_helper'
require 'support/utilities'
describe "User Pages" do
#include SessionsHelper
let(:user) { FactoryGirl.create(:user) }
let(:current_user) {user}
subject { page }
describe "Edit" do
before do
sign_in(user)
visit edit_user_path(user)
end
it '(check links and content)' do
should have_button('Submit')
should have_link('Cancel')
should have_content(user.fname+"\'s profile")
end
...
end
...
end
But current_user is still coming back nil
Any help/guidance is appreciated. Thanks!
Adding include SessionsHelper to the top describe block of my user_pages_edit.rb seems to try and use the sign_in(path) from that helper. Creating an issue between RSpec and cookies.permanent. So that's a bust.
unfortunately, this brings me right back to my .admin? error.
There are two calls to current_user.admin?
One is in the controller:
def path_switch
if current_user.admin? #error current_user == nil
users_path
else
root_path
end
end
One is in the view as ERB:
<% if current_user.admin? %>
<div class="row col-xs-6 col-sm-6 col-md-3">
<div class="input-group input-selector">
...
All I need to do is figure out how to set current_user.admin = true and pass it to the controller (and then hopefully the view) so that the page can load.
To do that, all I need to do is set current_user = user because user.admin == true.
If you are doing unit testing of your controller you can simply stub your current_user in a before block, like this:
let(:user) { ... }
# RSpec version <= 2 syntax:
before { controller.stub(:current_user) { user } }
# RSpec version >= 3 syntax:
before { allow(controller).to receive(:current_user) { user } }
If you are doing feature or request testing, I advise you to perform a real log-in by creating a user in your database, then passing through your log-in page with this user credentials
Here you seem to be doing a feature test, you should write a helper that perform the creation of the user record and go through the log-in.
Additionally in feature testing to gain a lot of time while running your test, do not hesitate to group your assertions in the same block. Clearly, instead of:
it { should have_button('Submit')}
it { should have_link('Cancel')}
it { should have_content(user.fname+"\'s profile")}
You can write
it 'check links and content' do
should have_button('Submit')
should have_link('Cancel')
should have_content(user.fname+"\'s profile")
end
That will avoid to generate several session of your feature environment, and also to log-in several times
Also works
user = create(:user) #FactoryBot
allow(controller).to receive(:current_user).and_return(user)
For me, worked with:
before { controller.stub!(:current_user).and_return(user) }
I run into the same problem with a legacy Rails 4 app and based my solution on this test case of Rspec Views.
First define a helper that will define the missing helper method in the controller instance
# /spec/support/concerns/view_helper.rb
module ViewHelper
def include_current_user_helper(&block)
controller.singleton_class.class_eval do
define_method :current_user, &block
helper_method :current_user
end
end
end
Then configure Rspec to include it in all the view helpers
# spec/rails_helper.rb
# ...
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
# ...
config.include ViewHelper, type: :view
end
And in the view specs it is called like this
RSpec.describe 'something' do
let!(:user) { FactoryGirl.create(:user) } # Note the "!" there
before { include_current_user_helper { user } }
# do stuff
end
Note: the call to let with bang is important as content inside the block will be executed lazily, outside of the test scope, and user will be nill if not
I'm going through Michael Hartl's Rails tutorial. I downloaded the source code samle_app_rails_4.
Then I add a test for signing out a user, it works fine in my browser, but in Rspec,
it fails. I changed nothing to the source code but add this to the user_pages_spec.rb.
require 'spec_helper'
describe "User pages" do
describe "sign out" do
let(:user) { FactoryGirl.create(:user) }
before :each do
sign_in user
end
it "should sign out a user" do
delete signout_path
expect(page).to have_content("Sign in")
end
end
end
The sign_in method is defined in spec/support/utilities.rb
include ApplicationHelper
def sign_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara as well.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
The sessions_controller.rb is like this( I ignored create action here.)
class SessionsController < ApplicationController
def new
end
def destroy
sign_out
redirect_to root_url
end
end
And sessions_helper.rbis like this:
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
end
I have tried many solutions, post an issue on github, and this Cookies do not persist in Rspec on rails 3.1
I also go through these questions but nothing is helpful. https://stackoverflow.com/questions/tagged/railstutorial.org.
I even upgrade my rspec and capybara gem. Maybe something wrong with my test file?
delete signout_path is not right for signing out a user?
I find it's weird in sample app, as an user has logged in, he can still visit signin_path and signup_path.
Wish someone can help me, big thanks!
The page variable you are accessing in your example is only available with use of Capybara methods such as visit and click. By directly issuing the HTTP delete operation, you are bypassing Capybara and the setting of the page variable.
You can access the response body as response.body using your example, but note that the tutorial shows a test of sign-out at http://ruby.railstutorial.org/book/ruby-on-rails-tutorial#code-signout_test using a different request spec and Capybara's click method.
As I read the code, SessionsController#destroy redirects to static_pages#home which does not contain the string "Sign in"
in your spec you redirect to root_url after signout the user, so you should have the string "Sign in" in the root_url, just add for example <h1>Sign in</h1> to your root_url view and it will be passing, if your root url don't contains the form to sign in, in this case you should redirect to signin_path (wich will be contains the string "Sign in")
Inside a controllers test, I want to test that when logged in, the controller renders the request fine, else if not logged in, it redirects to the login_path.
The first test passes fine as expected, no user is logged in, so the request is redirected to the login_path. However I've tried a myriad of stub/stub_chain's but still can't get the test to fake a user being logged in and render the page okay.
I would appreciate some direction on getting this to work as expected.
The following classes and tests are the bare bones to keep the question terse.
ApplicationController
class ApplicationController < ActionController::Base
include SessionsHelper
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
SessionsHelper
module SessionsHelper
def logged_in?
redirect_to login_path, :notice => "Please log in before continuing..." unless current_user
end
end
AppsController
class AppsController < ApplicationController
before_filter :logged_in?
def index
#title = "apps"
end
end
apps_controller_spec.rb
require 'spec_helper'
describe AppsController do
before do
#user = FactoryGirl.create(:user)
end
describe "Visit apps_path" do
it "should redirect to login path if not logged in" do
visit apps_path
current_path.should eq(login_path)
end
it "should get okay if logged in" do
#stubs here, I've tried many variations but can't get any to work
#stubbing the controller/ApplicationController/helper
ApplicationController.stub(:current_user).and_return(#user)
visit apps_path
current_path.should eq(apps_path)
end
end
end
This is not working because you are stubbing the method current_user on the ApplicationController class, and not an instance of that class.
I would suggest stubbing it (correctly) on an instance of that class, but your test appears to be an integration test rather than a controller test.
What I would do instead then is as Art Shayderov mentioned is to emulate the sign-in action for a user before attempting to visit a place that requires an authenticated user.
visit sign_in_path
fill_in "Username", :with => "some_guy"
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
In my applications, I've moved this into a helper method for my tests. This is placed into a file at spec/support/authentication_helpers.rb and looks like this:
module AuthenticationHelpers
def sign_in_as!(user)
visit sign_in_path
fill_in "Username", :with => user.username
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
end
end
RSpec.configure do |c|
c.include AuthenticationHelpers, :type => :request
end
Then in my request specs, I simply call the method to sign in as that particular user:
sign_in_as(user)
Now if you want to sign in using a standard controller test, Devise already has helpers for this. I generally include these in the same file (spec/support/authentication_helpers.rb):
RSpec.configure do |c|
c.include Devise::TestHelpers, :type => :controller
end
Then you can sign in using the helpers like this:
before do
sign_in(:user, user)
end
it "performs an action" do
get :index
end
I would look at http://ruby.railstutorial.org/chapters/sign-in-sign-out#sec:a_working_sign_in_method.
The author describes how to write a sign_in method and use it in your rspec tests.
It doesn't look like controller test. It looks more like rspec-rails request spec which simulates browser. So stabbing controller won't work, you have to either simulate sign in (something like this)
visit sign_in
fill_in 'username', :with => 'username'
...
or manually add user_id to session.
If on the other hand you want to test controller in isolation your test should look like that:
get 'index'
response.should be_success