How to write an Rspec controller test with authentication? - ruby-on-rails

I have two models:
# app/models/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :teams, dependent: :destroy
end
and
# app/models/team.rb
class Team < ActiveRecord::Base
belongs_to :user
validates_presence_of :user
end
I am trying to test my TeamsController using Rspec and factory_girl_rails.
Before I can create a new Team I need an authenticated User.
I created a :user factory:
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Doe"
email {|n| "email#{n}#email.com" }
mobile_phone "1235551234"
company_name "Widgets Inc."
password "password"
end
end
Here are the relevant parts of teams_controller_spec.rb:
require 'rails_helper'
RSpec.describe TeamsController, type: :controller do
# This should return the minimal set of values that should be in the session
# in order to pass any filters (e.g. authentication) defined in
# TeamsController. Be sure to keep this updated too.
let(:valid_session) { {} }
describe "GET #index" do
it "assigns all teams as #teams" do
user = FactoryGirl.create(:user)
team = Team.create!(name: "New Team Name", user: user)
get :index, {}, valid_session
expect(assigns(:teams)).to eq([team])
end
end
end
The test is failing:
1) TeamsController GET #index assigns all teams as #teams
Failure/Error: get :index, {}, valid_session
NoMethodError:
undefined method `authenticate' for nil:NilClass
I don't understand how I need to populate :valid_session so that the test will pass. I thought I'd have to explicitly call an authenticate method but that might not be true. I'm trying to test the Team controller... not User authentication.
Any advice would be much appreciated.

I'd do this in your rails_helper:
module ControllerMacros
def sign_me_in
before :each do
#request.env['devise.mapping'] = Devise.mappings[:user]
#current_user = FactoryGirl.create(:user)
sign_in :user, #current_user
end
end
end
Rspec.configure do |config|
#other rspec stuff
config.include FactoryGirl::Syntax::Methods
config.extend ControllerMacros, type: :controller
config.include Devise::Test::ControllerHelpers, type: :controller
end
Then in your controller spec, (provided you're requiring your rails_helper) you can just to sign_me_in whenever you want to be signed in and not bother about the valid_session:
RSpec.describe TeamsController, type: :controller do
sign_me_in
#etc...
end
However in your specific case you want to know who you're signed in as, so you can do this:
RSpec.describe TeamsController, type: :controller do
describe "GET #index" do
it "assigns all teams as #teams" do
user = FactoryGirl.create(:user)
team = Team.create!(name: "New Team Name", user: user)
#request.env['devise.mapping'] = Devise.mappings[:user]
sign_in :user, user
get :index
expect(assigns(:teams)).to eq([team])
end
end
end
The devise mappings line may not be required in your case, but can't say without inspecting your full app.

Related

Create a model method that counts a user's posts and test in rspec (Rails)?

I'm trying to create a model method that counts the number of posts for a user, and then test it with Rspec.
But I'm running into an error,
undefined method `count_posts' for #<User:0x000000044d42a8>
User Model
has_many :posts
def self.count_posts
self.posts.all.count
end
Posts Model
belongs_to :user
User_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe "count_posts" do
before do
#user1 = create(:user)
post = create(:post, user: #user1)
end
it "Returns number of posts for a user" do
expect( #user1.count_posts ).to eq(1)
end
end
end
/factories/users.rb
FactoryGirl.define do
factory :user do
sequence(:email, 100) { |n| "person#{n}#example.com"}
password "helloworld"
password_confirmation "helloworld"
end
end
/factories/posts.rb
FactoryGirl.define do
factory :post do
title "Post Title"
body "Post bodies must be pretty long."
user
end
end
I don't understand why its an undefined method, unless I've written it incorrectly in the model (which I fully accept as possible).
Apologies in advance if this question is too newbish. But I haven't fully grasped Rspec testing or the use of self.
According to your logic, count_posts must be an instance method instead of class method:
class User < ActiveRecord::Base
has_many :posts
def count_posts
posts.count # or posts.size
end
end

Test Devise's registration with Rspec and FactoryGirl

Routes
devise_for :users, controllers: { registrations: 'users/registrations' }
Models
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
before_create :set_default_role
enum role_types: [:doctor, :patient]
def set_default_role
set_role :patient unless roles.include? User.role_types[:patient]
end
end
class Patient < User
end
class Doctor < User
end
FactoryGirl
require 'faker'
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "full_name_#{n}" }
address 'valid address'
phone '1234567890'
password 'password'
sequence(:email) { |n| "valid_email_#{n}#example.com" }
end
factory :doctor, class: Doctor, parent: :user do
roles [0, 1]
field
end
factory :patient, class: Patient, parent: :user do
roles [1]
end
end
Registration's spec
require 'spec_helper'
describe Users::RegistrationsController, type: :controller do
before do
#request.env['devise.mapping'] = Devise.mappings[:user]
end
context 'register a patient' do
let(:add_patient) { post :create, patient: FactoryGirl.attributes_for(:patient) }
it { expect { add_patient }.to change(Patient, :count).by(1) }
end
context 'register a doctor' do
let(:add_doctor) { post :create, doctor: FactoryGirl.attributes_for(:doctor) }
it { expect { add_doctor }.to change(Doctor, :count).by(1) }
end
end
When I run the spec, I keep getting this failure for both examples
1) Users::RegistrationsController register a patient should change #count by 1
Failure/Error: it { expect { add_patient }.to change(Patient, :count).by(1) }
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/registrations_controller_spec.rb:19:in `block (3 levels) in <top (required)>'
My questions are, what causes the failure and how do I debug if my post request is successfully create a user? I know I can use capybara, but in this case I want to make sure if I do the post request for registration, it will register a user.

Testing User visiting his own profile with rspec capybara, Warden gems on rails

I'm new to Ruby on rails and programming in general.
In an assignment I'm doing, I was asked to create a test where a User visits his on profile.
Rspec, Devise and capybara gems are installed.
Here is my profiles_spec:
require 'rails_helper'
describe "Visiting profiles" do
include TestFactories
before do
#user = authenticated_user
#post = associated_post(user: #user)
#comment = Comment.new(user: #user, post: #post, body:"A comment")
allow(#comment).to receive(:send_favorite_emails)
#comment.save
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
end
describe "not signed in" do
it "shows profile" do
visit user_path(#user)
expect(current_path).to eq(user_path(#user))
expect(page).to have_content(#user.name)
expect(page).to have_content(#post.title)
expect(page).to have_content(#comment.body)
end
end
describe "user visting own profile" do
it "shows profile" do
visit user_path(current_user)
expect(current_path).to eq(user_path(user))
expect(page).to have_content(user.name)
expect(page).to have_content(#post.title)
expect(page).to have_content(#comment.body)
end
end
end
Here is my TestFactories:
module TestFactories
include Warden::Test::Helpers
Warden.test_mode!
def associated_post(options = {})
post_options = {
title: 'Post title',
body: 'Post bodies must be pretty long.',
topic: Topic.create(name: 'Topic name',description: 'the description of a topic must be long'),
user: authenticated_user
}.merge(options)
Post.create(post_options)
end
def authenticated_user(options = {})
user_options = { email: "email#{rand}#fake.com", password: 'password'}.merge(options)
user = User.new( user_options)
user.skip_confirmation!
user.save
user
end
FactoryGirl.define do
factory :user do
email 'test#example.com'
password 'f4k3p455w0rd'
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
# if needed
# is_active true
end
end
end
Here is my User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :votes, dependent: :destroy
has_many :favorites, dependent: :destroy
mount_uploader :avatar, AvatarUploader
def admin?
role == 'admin'
end
def moderator?
role == 'moderator'
end
def favorited(post)
favorites.where(post: post.id).first
end
def voted(post)
votes.where(post: post.id).first
end
end
When I run the profiles test, I get this error:
`<module:TestFactories>': uninitialized constant TestFactories::FactoryGirl (NameError)
I'm not sure if i'm using warden the right way.
Thank you.
The error is because you have not included FactoryGirl In your TestFactories-module.
Your factory looks pretty messy.
In my projects I have a folder structure with spec/factories where I put factories.
For example I would name this users.rb:
FactoryGirl.define do
factory :user do
email
password '12345678'
end
end
To skip confirmation you can add:
confirmed_at: { Time.zone.now}
In a separate factory called shared
I put:
sequence(:email) { |n| "name#{n}#domain.se" }
Then when I want a user in a test I use
let(:user) { FactoryGirl.create(:user
And you should be able to use
login_as
I'm not sure how that work because I mostly use CanCanCommunity.
Sorry for messy post, written on phone.
The uninitialized constant...FactoryGirl error message says that FactoryGirl isn't available.
The options are to install and configure the factory girl gem, or to avoid it until its needed.
For this spec, it looks like factory girl isn't adding anything, so consider removing these references to it (and avoid installing the factory girl gem for now):
The line user = FactoryGirl.create(:user) from profiles_spec.rb
The entire FactoryGirl.define do...end block in TestFactories
It appears that the existing #user object is adequate for what the spec needs. user and current_user don't seem to do anything that #user couldn't be used for. Apologies if I've missed something.

CanCan in RSpec Controller spec

I spent most of the day trying to root out a problem with a controller spec, and the current workaround seems unacceptable to me. Any take on why this works? ... and what I should do instead.
Given a simple hierarchy as follows, and the following ability.rb, the properties_controller_spec.rb does not allow the spec below to pass without the line saying:
ability = Ability.new(subject.current_user)
Can you tell me why this would be?
Thanks!
Models:
class Account < ActiveRecord::Base
has_many :properties, :dependent => :nullify
end
class Property < ActiveRecord::Base
belongs_to :account
end
class User < Refinery::Core::BaseModel #for RefineryCMS integration
belongs_to :account
end
Ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.has_role? :user
can [:read, :create, :update, :destroy], Property, account_id: user.account_id
else
can [:show], Property
end
end
end
properties_contoller_spec.rb:
require 'spec_helper'
describe PropertiesController do
def valid_attributes
describe "Authenticated as Property user" do
describe "PUT update" do
describe "with invalid params" do
it "re-renders the 'edit' template" do
property = FactoryGirl.create(:property, account: property_user.account)
# Trigger the behavior that occurs when invalid params are submitted
Property.any_instance.stub(:save).and_return(false)
ability = Ability.new(subject.current_user) # seriously?
put :update, {:id => property.to_param, :property => { }}, {}
response.should render_template("edit")
end
end
end
end
end
Arg! Found it myself.
Here it is:
config.include Devise::TestHelpers, :type => :controller
Following is the code to sign in the property_user, as directed by the Devise docs. (The locals in question are created in a global_variables.rb that is included. These are used all over the place.)
def signed_in_as_a_property_user
property_user.add_role "User"
sign_in property_user
end
def sign_in_as_a_property_user
property_user.add_role 'User'
post_via_redirect user_session_path,
'user[email]' => property_user.email,
'user[password]' => property_user.password
end

Rspec factory girl issue

I have an rspec/factory girl test where I can't get a test to pass.
I'm using devise where current_user calls the currently logged in User model.
I can load up a test console and type in
u = Factory(:user)
u.company
And this will return a valid company but for some reason in rspec calling current_user.company is returning nil.
Any ideas?
Controller
class CompaniesController < ApplicationController
before_filter :authenticate_user!
def show
#company = current_user.company
end
end
Model
class User < ActiveRecord::Base
validates_uniqueness_of :email, :case_sensitive => false
has_one :company
end
Factory
Factory.define :company do |f|
f.name 'Test Company'
end
Factory.sequence(:email) do |n|
"person#{n}#example.com"
end
Factory.define :user do |f|
f.name 'Test User'
f.email {Factory.next :email}
f.password 'password'
f.company Factory(:company)
end
Test
describe CompaniesController do
before(:each) do
#user = Factory(:user)
sign_in #user
end
describe "GET show" do
before do
get :show
end
it "should find the users company" do
assigns(:company).should be_a(Company)
end
end
end
Spec Helper
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
config.infer_base_class_for_anonymous_controllers = false
end
Test Result
Failures:
1) CompaniesController GET show should find the users company
Failure/Error: assigns(:company).should be_a(Company)
expected nil to be a kind of Company(id: integer, name: string, user_id: integer, created_at: datetime, updated_at: datetime)
# ./spec/controllers/companies_controller_spec.rb:21:in `block (3 levels) in <top (required)>'
EDIT
I have removed the f.company = Factory(:company) in the factories file. And made my controller spec this
require 'spec_helper'
describe CompaniesController do
let(:current_user) { Factory(:user) }
before(:each) do
sign_in current_user
current_user.company = Factory(:company)
current_user.save
end
describe "GET show" do
before do
get :show
end
it "should find the users company" do
current_user.should respond_to(:company)
assigns(:company).should == current_user.company
end
end
end
I'm not sure but I believe assigns(:company) checks for an instance variable #company which obviously doesn't exist. Try putting #company = #user.company in your before(:each) block or test for it in another way, for example;
it "should find the users company" do
#user.should respond_to(:company)
end
I believe that should do it!
Define Let object for company in your controller rspec.
describe CompaniesController do
describe "authorizations" do
before(:each) do
let(:company) { Factory :company }
let(:user_admin) { Factory(:user) }
end
it "should redirect" do
sign_in(user_admin)
get :show
end
it "should find the users company" do
assigns(:company).should be_a(company)
end
end
end
Can you try with above spec ?
I think the main thing you were missing is setting up an association in your factory. Starting from your original example:
Factory.define :user do |f|
f.name 'Test User'
f.email {Factory.next :email}
f.password 'password'
f.association :company, factory => :company
end
Then when you create a user, it will create a company and fill in user.company_id with the proper id.
See "Associations" in the Factory Girl Getting Started doc.

Resources