I am using factory_girl_rails (4.2.1) and rspec-rails (2.14.0) to test a simple controller on Rails 4. When testing an error case, I use FactoryGirl.build to build an invalid User object. However, the resulting object does not contain any error in #user.errors; yet expect(assigns(:user)).to have(1).errors_on(:email) in the test case still passes. Why doesn't the FactoryGirl generated object has any error, and how does rspec see the error?
Here are the details and code.
The controller simply creates a User object, then redirect to a verification page if creation was successful or render the form again if there is any error.
class RegistrationController < ApplicationController
def new
end
def create
#user = User.create(params.required(:user).permit(:email, :password, :password_confirmation))
if #user.errors.empty?
redirect_to verify_registration_path
else
render :new
end
end
end
In my error case test, I create a User without 'email' using FactoryGirl. It is expected to create an error entry in #user.errors for the 'email' field AND renders the :new template.
describe RegistrationController do
#... Some other examples ...
describe 'GET create' do
def post_create(user_params)
allow(User).to receive(:create).with(ActionController::Parameters.new({user: user_params})[:user]).and_return(FactoryGirl.build(:user, user_params))
post :create, user: user_params
end
context 'without email' do
before { post_create email: '', password: 'testing', password_confirmation: 'testing' }
subject { assigns(:user) }
it 'build the User with error' do
expect(subject).to have(1).errors_on(:email)
end
it 'renders the registration form' do
expect(response).to render_template('new')
end
end
end
end
However, when I ran the test case, only the 'renders the registration form' example failed, but not the other one.
Failures:
1) RegistrationController GET create without email renders the registration form
Failure/Error: expect(response).to render_template('new')
expecting <"new"> but rendering with <[]>
# ./spec/controllers/registration_controller_spec.rb:51:in `block (4 levels) in <top (required)>'
Finished in 0.25726 seconds
6 examples, 1 failure
Failed examples:
rspec ./spec/controllers/registration_controller_spec.rb:50 # RegistrationController GET create without email renders the registration form
What is strange here is that rspec seems to be able to see an error in #user (hence the first test case passes) but for some unknown reason #user.error.empty? returns true in controller causing it to redirect instead of rendering the :new template (hence the failed second test case). I also confirmed in debugger that #user.error was indeed empty.
Is it something wrong with how FactoryGirl handles error, or am I using it wrong?
Thanks
Two things I want to mention here are:
1. Probably You want to use "Post create" instead of "Get create".
2. Whether email is missing or not is the model's concern, not controller's.
I suggest you use stub to return false for the case that email is missing.
The easiest way is:
User.any_instance.stub(:create).and_return(false)
And maybe you want to change some other things in the controller, like "if #user.errors.empty?"
EDIT: Sorry, "create" actually doesn't return false.
So in your controller
#user = User.new(.....)
if #user.save
...
else
render :new
And in your test use
User.any_instance.stub(:save).and_return(false)
Related
I have following code
#support_report.notes = params[:support_report][:notes]
if #support_report.save
redirect_to admins_support_reports_path, notice: t('admins.support_reports.add_notes.successfully')
else
redirect_to admins_support_reports_path, alert: t('admins.support_reports.add_notes.no_change')
end
In this am just updating 'notes' attribute which is not having any validation.
I have to cover 'else' part inside rspec coverage.
How can I do it, please suggest some way
it "should re-render new template on failed save" do
SupportReport.any_instance.stubs(:valid?).returns(false)
post 'create'
assigns[:support_report].should be_new_record
response.should render_template('new')
end
I'm having a bizarre problem where I create an admin user for an Rspec/Capybara test and the user is mysteriously deleted after logging in. When I run my test, the user is created and successfully logged in. However when Capybara visits the admin_categories_path, the test fails. Rails raises an exception because current_user is not defined. When I insert a binding.pry, I can see that the user exists up until they are logged in, at which point the user disappears from the test database, causing current_user to be undefined, and thereby triggering an exception. I'm at a loss as to how/why this is happening.
UPDATE: I've gotten past the problem of the user being deleted. Now when Capybara sees the admin_categories_path page, category isn't displayed. Inserting a binding.pry reveals that category is present in the database.
When I launch the app and log in manually, I have no problems accessing admin_categories_path.
Here is my spec file:
require 'rails_helper'
feature 'admin edits category', %Q{
As an admin, I want to edit a category or subcategory, so that it better
represents the content under it.
Acceptance Criteria:
* [X] - I can edit the name of a category inline.
* [X] - I can edit the name of a subcategory inline.
} do
let(:admin) do
FactoryGirl.create(:user, admin: true)
end
let(:category) do
FactoryGirl.create(:category)
end
scenario 'admin edits category title', js: true, focus: true do
login_as(admin)
visit admin_categories_path
bip_area category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
end
Here is my helper file authentication.rd:
module Helpers
module Authentication
def log_in_as(user)
visit new_user_session_path
within "#new_user" do
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: user.password
click_on "Log in"
end
end
end
RSpec.configure do |config|
config.include Authentication, :type => :feature
end
end
Here is my user factory:
require 'factory_girl'
FactoryGirl.define do
factory :user do
sequence(:email) {|n| "lafiel.abriel#{n}#abhnation.com" }
username
password 'password'
password_confirmation 'password'
end
factory :category do
sequence(:name) { |n| "Category ##{n}" }
parent_id nil
display_index 1
user
end
sequence :username do |n|
"Lafiel_Abriel_#{n}"
end
end
And here is my application_controller.rd where the exception is triggered:
module Admin
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :reject_unless_admin
layout 'admin/layouts/application'
protect_from_forgery with: :exception
helper :avatar, :devise, :admin
protected
def reject_unless_admin
unless current_user && current_user.admin?
raise ActionController::RoutingError.new('404: Not Found')
end
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << [
:avatar,
:remote_avatar_url,
:email,
:username,
:first_name,
:last_name,
:age,
:website,
:password,
:password_confirmation,
:current_password
]
end
end
end
Any help/insight is greatly appreciated. Thanks.
If I recall correctly, this problem had something to do with the test database schema being out of sync with the development database. Try running rake db:test:prepare to ensure your test schema matches your development schema.
In my specific case, I had a two problems to overcome.
The first problem occurred when I created a category and an admin in a let block. Those needed to be placed in a before block so that they were created before the test executed. When I placed those variables in let blocks, they were not being created until the first time they were called in the test. So when I logged in as an admin, the admin variable was being passed to my login_as helper method before it was saved to the database. The same is true for the category variable.
This code doesn't work for me:
let(:admin) do
FactoryGirl.create(:user, admin: true)
end
let(:category) do
FactoryGirl.create(:category)
end
scenario 'admin edits category title', js: true, focus: true do
login_as(admin)
visit admin_categories_path
bip_area category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
This is solution I used to overcome the first problem:
before(:each) do
#admin = FactoryGirl.create(:user, admin: true)
#category = FactoryGirl.create(:category, user: #admin)
end
scenario 'admin edits category title', js: true do
skip "Doesn't work yet."
login_as(#admin)
visit admin_categories_path
wait_for_ajax
bip_text #category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
The second problem is that when my test hits the admin/categories_controller.rb, Category.all returns an empty array, even though #category.save! and #category.valid? return true when I test my before block. I was never able to find a solution to the problem and ended up putting the test on ice.
I think part of the problem is that the documentation for the Best-in-Place gem is not very good. I used it at the time because it was an easy solution to my problem. Going forward though, I would use something like angular for this use-case. The documentation is much better and it's not a black box like a lot of gems are.
I'm not sure what bip_area does, but since your test says that it's editing a category I assume you are expecting the category to be present on the page when you visit admin_categories_path. When you visit that path however category has not yet been created since you are using 'let' which is lazily evaluated (created at time of first use of the variable), so it would not be shown on the screen for you to edit. Using binding.pry and then looking at that variable would actually create it, so it might be confusing you into thinking it was there. You can use 'let!' instead which will force the variable to be created before each test instead of lazily evaluating it.
I have a dashboard containing 2 partials. One partial works fine and does what its suppose to (bill). The other partial is setup similar to bill but wont work(request). When I look at my log it shows that the tenant(user) is being queried, also, the 1st partial is queried but the 2nd partial doesn't query. when I went to my dashboard controller and changed the instance of the partial to (Request.new) it works but I can't seem to get it to work right thru the controller. I don't want to have the method in the model. I am using mongoid.
SO here is my render in the dashboard...
<%= render partial: "request", locals: {request: #request} %>
In the partial I have...
<%= form_for [:tenants, request] do |f| %>
And on the dashboard controller I have...
def show
#dashboard = current_tenant
#bill = current_tenant.bill || current_tenant.build_bill
#request = current_tenant.request || current_tenant.build_request
end
(if I change #request = Request.new it works fine but I know that's not right)
The bill partial works and the dashboard finds the tenant but I keep getting
"undefined method `request' for #
Any idea of what I am missing? I compared the bill controller to the request controller and I cant find any differences. When I put the Model.new into the dashboard controller it works but I know this isn't right, its as if the app wont recognize the Request controller...
The error is saying it doesn't recognize "request" method.
Also here is my controller for request...
class Tenants::RequestsController < ApplicationController
before_filter :authenticate_tenant!
def index
#requests = Request.all
end
def show
#request = Request.find(params[:id])
end
def create
if #request = current_tenant.create_request(authorization_params)
redirect_to tenants_dashboard_path, :notice => "#{request.manager_name} has been Authorized!"
else
redirect_to tenants_dashboard_path, :error => "#{request.manager_name} has NOT been Authorized, please try again."
end
end
def edit
end
def update
if current_tenant.request.update_attributes(authorization_params)
redirect_to tenants_dashboard_path, :notice => "You have approved #{request.manager_name} to review your report"
else
redirect_to tenants_dashboard_path, :notice => "#{request.manager_name} is NOT allowed to review your report"
end
end
def destroy
#request = Request.find(params[:request_id])
name = #request.name
if #request.destroy
flash[:notice] = "\"#{name}\" was successfully removed from your profile."
redirect_to #dashboard
else
flash[:error] = "There was an error deleting this managers access."
render :show
end
end
Well it looks like
current_tenant.request has an issue. That means that the method is not available. Assuming you're not trying to access the http request , then you have an issue with the request method.
So your issue is with how you defined the request method (maybe in your model). e.g. is it a class method or a instance method etc.
Without knowing your goal, that's the general answer I can give you. Creating a Request.new could be right depending on your goal, but if your goal is to call the request method, you must make it available to current_tenant
One controller shouldn't be calling your other controller as you have suggested...
So I'm running through the Michael Hartl Rails tutorial and just after creating the edit page (end of Listing 9.3), I get the following errors when I run my tests. As far as I know, I defined the sign_in method and make it available in my application controller. Not exactly sure where I went wrong as his tutorial says my tests should pass by this point. Any help is appreciated. (Also, please let me know if I need to include anything else)
Failures:
1) User pages edit page
Failure/Error: sign_in user
NoMethodError:
undefined method sign_in' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_4::Nested_1:0x00000108a92118>
# ./spec/requests/user_pages_spec.rb:60:inblock (3 levels) in '
2) User pages edit page
Failure/Error: sign_in user
NoMethodError:
undefined method sign_in' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_4::Nested_1:0x00000108820560>
# ./spec/requests/user_pages_spec.rb:60:inblock (3 levels) in '
3) User pages edit page
Failure/Error: sign_in user
NoMethodError:
undefined method sign_in' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_4::Nested_1:0x00000108846be8>
# ./spec/requests/user_pages_spec.rb:60:inblock (3 levels) in '
4) User pages edit with invalid information
Failure/Error: sign_in user
NoMethodError:
undefined method sign_in' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_4::Nested_2:0x00000103e6cd60>
# ./spec/requests/user_pages_spec.rb:60:inblock (3 levels) in '
Finished in 0.98841 seconds
23 examples, 4 failures
Test Document: user_pages_spec.rb
require 'spec_helper'
describe "User pages" do
subject { page }
.
.
.
describe "edit" do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
visit edit_user_path(user)
end
describe "page" do
it { should have_content("Update your profile") }
it { should have_title("Edit user") }
it { should have_link('change', href: 'http://gravatar.com/emails') }
end
describe "with invalid information" do
before { click_button "Save changes" }
it { should have_content('error') }
end
end
end
Session Helper: sessions_helper.rb
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 sign_out
current_user.update_attribute(:remember_token,
User.encrypt(User.new_remember_token))
cookies.delete(:remember_token)
self.current_user = nil
end
end
Application Controller: application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
end
This is telling you that sign_in isn't a method in the scope of the test so you can't use it in the before block like that. It's a controller method not a test method.
Simplest approach is to create a spec helper method which does the signing in, which you can call in the context of a before block which sets up state for any test requiring a signed in user.
One way to do this would be I have a "user_signs_in" helper which uses capybara to visit signin path, fill in signin in form with correct details, and then follow redirects.
Related stackoverflow q:
How do I simulate a login with RSpec?
When I try to register an user, it does not give me any error but just cannot save the user.
I don't have attr_accessible. I'm not sure what I am missing. Please help me.
user.rb
class User < ActiveRecord::Base
has_secure_password
validates :email, presence: true,
uniqueness: true,
format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
validates :password, presence: true, length: {minimum: 6}
validates :nickname, presence: true, uniqueness: true
end
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params) # Not saving #user ...
if #user.save
flash[:success] = "Successfully registered"
redirect_to videos_path
else
flash[:error] = "Cannot create an user, check the input and try again"
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :nickname)
end
end
Log:
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5OqMgarqMFj17dVSuA8tVueg1dncS3YtkCfMzMpOUE=", "user"=>{"email"=>"example#example.com", "password"=>"[FILTERED]", "nickname"=>"example"}, "commit"=>"Register"}
(0.1ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'example#example.com' LIMIT 1
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE "users"."nickname" = 'example' LIMIT 1
(0.1ms) rollback transaction
Regarding our short discussion in the comments, it appears that one or two things are happening to cause #user.save to return false:
One of the validation rules are failing
A callback within your model is returning false, thus halting processing and rolling back the transaction
There are a few quick ways to debug the validations. I figured I could describe them so you could learn a few options.
A. Change the call within the if statement to instead use the bang method of save:
if #user.save!
This will cause the app to raise an exception if validation fails, displaying the validation errors within the browser on your screen. In this particular scenario, you'd want to remember to remove the ! after you're done debugging because you probably don't want the final version of your app doing that.
Or...
B. Within the else statement, add this line:
raise #user.errors.to_yaml
This will display the validation errors within the browser on the screen. Of course, remember to remove this line after you're done debugging.
Or...
C. Within the else statement, add this line and then run the form post:
puts #user.errors.to_yaml
This will display the validation errors within your console. You'll want to remember to remove this line after you're done debugging, but it's "less worse" if you happen to forget because at least the extra info is only output to STDOUT.
You may want to try each of these just to get a little practice and to see what your options are in simple debugging scenarios like this.
High chances that error is in password confirmation. You use has_secure_password from Rails, which automagically handles password confirmation for you. And here is the problem - you don't have it before user creation. Thus just add. For details check out similar question on has_secure_password
And check, that you have password_digest:string in users table :)