Two things which i don't understand at all.
1) first example: visit path - fail and get path - pass, why?
Visit - capybara and get - rspec helper, right?
describe "in the Users controller" do
describe "visiting the users page as non-signed" do
before { get users_path }
#before { visit users_path }
it { expect(response.status).to eql(302) }
it { expect(response).to redirect_to(new_user_session_path) }
end
describe "visiting the user[:id = 1] profile page as non-signed" do
before { get user_path(User.where(admin: true)) }
#before { visit user_path(User.where(admin: true)) }
it { expect(response.status).to eql(302) }
it { expect(response).to redirect_to(new_user_session_path) }
end
end
With get some_path_here -> test pass
But with visit some_path_here ->
2) second example:
after login as regular user, should not have menu like admin.
It looks like no differense between user and admin
describe "as signed admin" do
let(:admin) { create(:admin) }
before do
log_in admin
end
it { should have_link("Users", href: users_path)}
it { should have_link("Orders", href: orders_path)}
it { should have_link("Current Menu", href: products_path)}
it { should_not have_link("Dashboard", href: new_order_path)}
end
describe "as signed user" do
let(:user) { create(:user) }
before do
log_in user
end
it { should have_link("Profile", href: user_path(user))}
it { should have_link("Dashboard", href: new_order_path)}
it { should_not have_link("Users", href: users_path)}
it { should_not have_link("Current Menu", href: products_path)}
end
include ApplicationHelper
def log_in(user)
visit root_path
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_button 'Sign in'
end
def sign_up(user)
visit new_user_registration_path
fill_in 'Username', with: user.username
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
fill_in 'Password confirmation', with: user.password
click_button 'Sign up'
end
EDIT 1
I do like that..and i can't get it...what's wrong with that..?
let(:admin) { create(:admin) }
let(:user) { create(:user) }
factory :user do
sequence(:username) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "qwerty"
password_confirmation "qwerty"
factory :admin do
admin true
end
end
and my view
- if user_signed_in?
%ul.nav.navbar-nav
%li
=link_to "Profile", current_user
- if !current_user.admin?
#if !current_user.try(:admin?)
%li
=link_to "Dashboard", new_order_path
- if !Order.get_order_for_user(current_user).nil?
%li
%a{:href => order_path(Order.get_order_for_user current_user)} Order
- else
%li
%a{:href => users_path} Users
%li
%a{:href => orders_path } Orders
%li
%a{:href => products_path } Current Menu
%ul.nav.navbar-nav.navbar-right
%li
= link_to "Sign Out", destroy_user_session_path, :method => :delete
it looks fine for me,but maybe i miss something...
EDIT 2
short version of db/shcema:
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
t.string "avatar"
t.boolean "admin"
end
and my User model:
before_save :set_default_role
private
# Set first user as Admin
def set_default_role
if User.count == 0
self.admin = true
end
end
EDIT 3 - last one! ;)
i save my before_save :set_default_role
but in my test, i do that:
# User model -> before_save make first user admin.
let(:admin) { create(:user) }
let(:non_admin) { create(:user) }
before do
sign_up admin
log_in non_admin
end
I understand it can be not ideal, but it works and that's fine for my prog.level. But if somebody have BP solution i will take that notice ;)
1) When using Capybara you use visit and page, when using plain rails integration tests (which RSpec request specs are a wrapper around) you use get and response - you can't mix and match those like using visit and response together. Also, in Capybara most drivers don't provide access to things like request response codes and whether or not the page was redirected since it's designed to test from a users perspective, which implies replying only on things that appear in the browser.
2) From your errors it appears a normal user is behaving exactly like an admin user which could be caused be a few things depending on what you're actually doing on the page. The simplest explanation would be that your conditional check for whether or not the user is an admin isn't implemented correctly, either in your model, or in the logic you have in your view.
Related
I am using RSpec to test my controller actions and have been successfully tested my index, show, edit actions so far. But for create action it is giving me the following error for valid attributes. I'm using rails 5 and ruby 2.5.3. Can't understand what am I doing wrong.
file /spec/factories/leaves.rb
FactoryBot.define do
factory :leave do
id {Faker::Number.between(from = 1, to = 3)}
user_id {Faker::Number.between(from = 1, to = 3)}
team_lead_id {Faker::Number.between(from = 1, to = 3)}
fiscal_year_id {Faker::Number.between(from = 1, to = 3)}
start_day {Date.today - Faker::Number.number(3).to_i.days}
end_day {Date.today - Faker::Number.number(3).to_i.days}
reason {Faker::Lorem.sentences(sentence_count = 3, supplemental = false)}
status {Faker::Number.between(from = 1, to = 3)}
factory :invalid_leave do
user_id nil
end
end
end
file /spec/controllers/leave_controller_spec.rb
context 'with valid attributes' do
it 'saves the new leave in the database' do
leave_params = FactoryBot.attributes_for(:leave)
expect{ post :create, params: {leave: leave_params}}.to change(Leave,:count).by(1)
end
it 'redirects to leave#index' do
render_template :index
end
end
file /app/controller/leave_controller.rb
def create
#leave = Leave.new(leave_params)
if #leave.save
flash[:notice] = t('leave.leave_create')
redirect_to leave_index_path
else
flash[:notice] = t('leave.leave_create_error')
redirect_to leave_index_path
end
end
The error is:
LeaveController POST#create with valid attributes saves the new leave in the database
Failure/Error: expect{ post :create, params: {leave: leave_params}}.to change(Leave,:count).by(1)
expected `Leave.count` to have changed by 1, but was changed by 0
# ./spec/controllers/leave_controller_spec.rb:64:in `block (4 levels) in <top (required)>'
Update Leave Database
create_table "leaves", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "team_lead_id", null: false
t.integer "fiscal_year_id", null: false
t.date "start_day", null: false
t.date "end_day", null: false
t.text "reason", null: false
t.integer "status", null: false
t.string "comment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Leave Model
class Leave < ApplicationRecord
validates :user_id, :team_lead_id, :fiscal_year_id, :start_day, :end_day, :reason, :status, presence: true
end
I think that might be because of id which you set in factory. You shouldn't set id attribute in factory. That's why number of Leave objects didn't change.
Additionally, I assume that you have some relations there - user_id, team_lead_id etc. If these relations are necessarry to create leave object then you have to create factories for these models, too.
In the end your factory should look like this
FactoryBot.define do
factory :leave do
user
team_lead
fiscal_year
start_day {Date.today - Faker::Number.number(3).to_i.days}
end_day {Date.today - Faker::Number.number(3).to_i.days}
reason {Faker::Lorem.sentences(sentence_count = 3, supplemental = false)}
status {Faker::Number.between(from = 1, to = 3)}
factory :invalid_leave do
user nil
end
end
end
Reference: Factory Bot documentation - associations
[For Future Reader] I got it to work by doing the fooling in leave_controller_spec.rb file.
describe 'POST#create' do
context 'with valid attributes' do
let(:valid_attribute) do
attributes_for(:leave,
user_id: 2,
team_lead_id: 3,
fiscal_year_id: 2,
start_day: '2018-10-10'.to_date,
end_day: '2018-10-10'.to_date,
reason: 'Sick',
status: 2)
end
it 'saves the new leave in the database' do
expect do
post :create, params: {leave: valid_attribute}
end.to change(Leave,:count).by(1)
end
it 'redirects to leave#index' do
render_template :index
end
end
I'm told this is the correct way to write a controller it block:
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
But I am surprised, because it doesn't seem DRY to me. Let's say I want to create two 'contexts' here. One where the user.update_attributes call returns false and returns true. Am I meant to simply copy paste two it blocks and tweak that one tiny argument?
describe UsersController do
let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
it "should pass 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 == "updated user"
response.status.should == 302
end
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 false
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
I was hoping rspec would allow me to write this sort of structure:
describe 'PATCH #update' do
before {}
context 'when attributes can be updated' do
before {}
it "should set the flash" do end
it "should set the status" do end
end
context 'when attributes can\'t be updated' do
before {}
it "should set the status" do end
it "should set the flash" do end
end
end
Note the multiple it blocks for the same contexts, because isn't one expectation per it block a good practise because it allows you to see exactly what's not working? How are you meant to do this with mocks?
When running my tests in RSpec, I get these errors:
Failures:
1) User micropost associations should have the right micropost in the right order
←[31mFailure/Error:←[0m ←[31m#user.microposts.should == [newer_micropost, older_micropost]←[0m
←[31mActiveRecord::StatementInvalid:←[0m
←[31mSQLite3::SQLException: no such column: micropost.created_at: SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = 1 ORD
ER BY micropost.created_at DESC←[0m
←[36m # C:in `load_target'←[0m
←[36m # ./spec/models/user_spec.rb:153:in `block (3 levels) in <top (required)>'←[0m
2) User micropost associations should destroy associated microposts
←[31mFailure/Error:←[0m ←[31mmicroposts = #user.microposts.dup←[0m
←[31mActiveRecord::StatementInvalid:←[0m
←[31mSQLite3::SQLException: no such column: micropost.created_at: SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = 1 ORD
ER BY micropost.created_at DESC←[0m
←[36m # C:in `load_target'←[0m
←[36m # ./spec/models/user_spec.rb:157:in `block (3 levels) in <top (required)>'←[0m
Finished in 5.18 seconds
←[31m97 examples, 2 failures←[0m
user_spec.rb:
require 'spec_helper'
describe User do
before do
#user = User.new(name: "John Smith", email: "john#example.com",
password: "password", password_confirmation: "password")
end
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should respond_to(:remember_token) }
it { should respond_to(:admin) }
it { should respond_to(:authenticate) }
it { should respond_to(:microposts) }
it { should be_valid }
it { should_not be_admin }
describe "micropost associations" do
before { #user.save }
let!(:older_micropost) do
FactoryGirl.create(:micropost, user: #user, created_at: 1.day.ago)
end
let!(:newer_micropost) do
FactoryGirl.create(:micropost, user: #user, created_at: 1.hour.ago)
end
it "should have the right micropost in the right order" do
# user.micropost returns an array
#user.microposts.should == [newer_micropost, older_micropost]
end
it "should destroy associated microposts" do
microposts = #user.microposts.dup
#user.destroy
microposts.should_not be_empty
microposts.each do |micropost|
Micropost.find_by_id(micropost.id).should be_nil
end
end
end
end
schema.rb (shortened):
ActiveRecord::Schema.define(:version => 20131002154740) do
create_table "microposts", :force => true do |t|
t.string "content"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "microposts", ["user_id", "created_at"], :name => "index_microposts_on_user_id_and_created_at"
create_table "users", :force => true do |t|
.
.
.
.
end
end
Micropost model:
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
validates :user_id, presence: true
# Ensures the microposts are in descending (DESC) order
# from newest to oldest, (SQL syntax).
default_scope order: 'micropost.created_at DESC'
end
I have run bundle exec rake db:migrate along with bundle exec rake db:test:prepare, I have also reset the database and tried it again but this didn't work.
Specifically the error reads: SQLite3::SQLException: no such column: micropost.created_at: SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = 1 ORD
ER BY micropost.created_at DESC
Well, the SQL problem is that the table name on your sort column is singular rather than plural.
i.e. "ORDER BY micropost.created_at" should read "ORDER BY microposts.created_at".
If you also post your Micropost and User model source code, I might be able to tell if this is caused by a bug in your model code or a bug in ActiveRecord.
Which version of Rails are you using?
I am running into this error when running my tests. I have checked to make sure all the email_confirmations are spelled correctly and (unless I am crazy) they are. I'm a bit of a Rails noob, so it could be something simple.
User Model
class User < ActiveRecord::Base
attr_accessible :email, :email_confirmation, :first_name, :last_name,
:password, :password_confirmation
has_secure_password
before_save { |user| user.email = email.downcase }
validates :first_name, presence: true, length: { maximum: 25 }
validates :last_name, presence: true, length: { maximum: 25 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :email_confirmation, presence: true
validates :password, presence: true, length: { maximum: 6 }
validates :password_confirmation, presence: true
end
Rspec tests
require 'spec_helper'
describe User do
before { #user = User.new(email: "user#example.com",
first_name: "John", last_name: "Smith",
password: "foobar", password_confirmation: "foobar",
email_confirmation: "user#example.com") }
subject { #user }
it { should respond_to(:first_name) }
it { should respond_to(:last_name) }
it { should respond_to(:email) }
it { should respond_to(:email_confirmation) }
it { should respond_to(:password_digest) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should respond_to(:authenticate) }
it { should be_valid }
describe "when first name is not present" do
before { #user.first_name = " " }
it { should_not be_valid }
end
describe "when last name is not present" do
before { #user.last_name = " " }
it { should_not be_valid }
end
describe "when email is not present" do
before { #user.email = #user.email_confirmation = " " }
it { should_not be_valid }
end
describe "when password is not present" do
before { #user.password = #user.password_confirmation = " " }
it { should_not be_valid }
end
describe "when first_name is too long" do
before { #user.first_name = "a" * 26 }
it { should_not be_valid }
end
describe "when last_name is too long" do
before { #user.last_name = "a" * 26 }
it { should_not be_valid }
end
describe "when email format is invalid" do
it "should be invalid" do
addresses = %w[user#foo,com user_at_foo.org example.user#foo.
foo#bar_baz.com foo#bar+baz.com]
addresses.each do |invalid_address|
#user.email = invalid_address
#user.should_not be_valid
end
end
end
describe "when email format is valid" do
it "should be valid" do
addresses = %w[user#foo.COM A_US-ER#f.b.org frst.lst#foo.jp a+b#baz.cn]
addresses.each do |valid_address|
#user.email = valid_address
#user.should be_valid
end
end
end
describe "when email address is already taken" do
before do
user_with_same_email = #user.dup
user_with_same_email.email = #user.email.upcase
user_with_same_email.save
end
it { should_not be_valid }
end
describe "when password doesn't match confirmation" do
before { #user.password_confirmation = "mismatch" }
it { should_not be_valid }
end
describe "when email doesn't match confirmation" do
before { #user.email_confirmation = "mismatch#example.com" }
it { should_not be_valid }
end
describe "when password confirmation is nil" do
before { #user.password_confirmation = nil }
it { should_not be_valid }
end
describe "when email confirmation is nil" do
before { #user.email_confirmation = nil }
it { should_not be_valid }
end
describe "with a password that's too short" do
before { #user.password = #user.password_confirmation = "a" * 5 }
it { should be_invalid }
end
describe "return value of authenticate method" do
before { #user.save }
let(:found_user) { User.find_by_email(#user.email) }
describe "with valid password" do
it { should == found_user.authenticate(#user.password) }
end
describe "with invalid password" do
let(:user_for_invalid_password) { found_user.authenticate("invalid") }
it { should_not == user_for_invalid_password }
specify { user_for_invalid_password.should be_false }
end
end
end
schema.rb
ActiveRecord::Schema.define(:version => 20130417021135) do
create_table "users", :force => true do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "password_digest"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
end
You're getting UnknownAttributeError because you don't have a column in your users table called email_confirmation. By default, ActiveRecord will look for DB columns named the same as the attributes you use to construct the model, but this line is trying to construct a User with an attribute the database doesn't know about:
before { #user = User.new(email: "user#example.com",
first_name: "John", last_name: "Smith",
password: "foobar", password_confirmation: "foobar",
email_confirmation: "user#example.com") }
Are you really intending to save the email confirmation in the database, or are you just wanting to check that it matches email before saving it? I assume the latter, and Rails actually has built-in support for doing just that:
class User < ActiveRecord::Base
validates :email, :confirmation => true
validates :email_confirmation, :presence => true
end
See more details on the Rails Guide to Validations, or the validates_confirmation_of API docs. (And you'll probably need to do the same thing for :password_confirmation.)
Having just spent a ton of time debugging my own instance of this, I thought I would chime in with a third possibility.
I had done a migration correctly and verified it by inspecting my ActiveRecord in the rails console. I had tried recreating my db from the schema many times and I had tried re-running the migration many times, all to no avail.
The problem, in my case, is that I was seeing the problem when running my unit tests, not at runtime. The issue is that my test database had gotten out of sync in my migration/rollback testing. The solution was quite simple. All I had to do was reset the test database with:
rake db:test:prepare
I understand that the answer above is marked correct and solves the OP's problem. But there is another cause for this error that goes unheeded in a number of stackoverflow posts on this topic. This error can occur in a polymorphic many to many when you forget to use the as: option to a has_many. For example:
class AProfile < ActiveRecord::Base
has_many :profile_students
has_many :students, through: :profile_students
end
class BProfile < ActiveRecord::Base
has_many :profile_students
has_many :students, through: :profile_students
end
class ProfileStudent < ActiveRecord::Base
belongs_to :profile, polymorphic: :true
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :profile_students
has_many :aprofiles, through: :profile_students
has_many :bprofiles, through: :profile_students
end
This will give you this error:
Getting “ActiveRecord::UnknownAttributeError: unknown attribute: profile_id
when you try to do the following:
a = AProfile.new
a.students << Student.new
The solution is to add the :as option to AProfile and BProfile:
class AProfile < ActiveRecord::Base
has_many :profile_students, as: :profile
has_many :students, through: :profile_students
end
class BProfile < ActiveRecord::Base
has_many :profile_students, as: :profile
has_many :students, through: :profile_students
end
I had the same issue and this worked like magic. Add this line at the end of your migration statements for each model you would update. Resets all the cached information about columns, which will cause them to be reloaded on the next request.
<ModelName>.reset_column_information
Reference : https://apidock.com/rails/ActiveRecord/Base/reset_column_information/class
I have the same message error and I fix ordering the params as same order of columns definition in database:
CONTROLLER
def create
worktime = Worktime.create(name: params[:name], workhours: params[:workhours], organization: #organization, workdays: params[:workdays])
render json: worktime
end
DATABASE
Table: worktimes
Columns:
id int(11) AI PK
name varchar(255)
workhours text
organization_id int(11)
workdays text
I had this error plenty of times when a table didn't have the column, but I had strange cause this time. For some reason, my migrations were perfect, but (weirdly) schema.rb hadn't updated, so rake db:migrate db:seed didn't create the column. I have no idea why that happened.
TL;DR, if your migrations are up to date, check schema.rb and make sure it is too
I want to write a test using Rspec and Capybara.
I have a Post model and I want to add a draft attribute to it. If the user checks that field the post is saved as draft (draft = true). Only posts that have he attribute draft = false will show in the post index page. Posts with draft = true will only show in the page of the user who created that post.
I've never made a Rspec + Capybara test in my life. So I was wondering if someone could tell me how to start or give me an example.
Thanks in advance!
schema.rb:
create_table "posts", :force => true do |t|
t.string "title"
t.string "content"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "comments_count", :default => 0, :null => false
t.datetime "published_at"
t.boolean "draft", :default => false
end
(By the way, do I need to post the model, controller or view pages?)
To add on to new2ruby's answer you can also use the Feature/Scenario aliases provided by capybara in your integration tests.
I find it reads nicely:
require 'spec_helper'
feature 'Visitor views posts' do
scenario 'shows completed posts' do
post = FactoryGirl.create(post)
visit posts_path
page.should have_content(post.title)
end
scenario 'does not show drafts' do
draft = FactoryGirl.create(draft)
visit posts_path
page.should_not have_content(draft.title)
end
end
I recently wrote a "getting started" blog post on using RSpec with Capybara for integration tests. Let me know if you have any questions on getting your codebase set up.
I've been following a basic pattern of model tests and integration tests. I've also been using FactoryGirl along with rspec and capybara. So... first off here is what my factory might look like:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}#example.com" }
password "foobar"
password_confirmation "foobar"
end
factory :post do
sequence(:title) { |n| "Test Title #{n}"}
string "Test content."
published_at Time.now()
comments_count 0
draft false
association :user
factory (:draft) do
draft true
end
end
end
Then I would make a model spec file (spec/models/post_spec.rb):
require 'spec_helper'
describe Post do
let(:post) { FactoryGirl.create(:post) }
subject { post }
it { should respond_to(:title) }
it { should respond_to(:content) }
it { should respond_to(:user_id) }
it { should respond_to(:user) }
it { should respond_to(:published_at) }
it { should respond_to(:draft) }
it { should respond_to(:comments_count) }
its(:draft) { should == false }
its(:comments_count) { should == false }
it { should be_valid }
end
Then I would make an integration test (spec/requests/posts_spec.rb):
require 'spec_helper'
describe "Posts pages" do
subject { page }
describe "index page when draft == false" do
let(:post) { FactoryGirl.create(:post) }
before { visit posts_path }
it { should have_content(post.title) }
end
describe "index page when draft == true" do
let(:draft) { FactoryGirl.create(:draft) }
before { visit posts_path }
it { should_not have_content(draft.title) }
end
end
You might try working through the rails tutorial at http://ruby.railstutorial.org/ It uses rspec, capybara and FactoryGirl for the tests.