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.
Related
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
I'd like to define a factory for a has_many/has_many relationship (which I think I got it right) but I don't know how then to define attributes for each objects of these created factory.
Here is the homepage, it's a list of cards of deals
We assume the homepage view below is for a SIGNED-IN user (note current_user comes from Devise).
<% #deals.each do |deal| %>
#userdeal = UserDeal.where('user_id = ? AND deal_id = ?', current_user.id, deal.id).take
<div>
<div class="button">
<% if #userdeal.number_of_clicks = 4 %>you reached the maximum clicks
<% end %>
</div>
</div>
<% end %>
Basically if a user has for a specific deal (on the table user_deals, see below the models) a user_deal.number_of_clicks = 4 then, on the card a button will appear with a message like "you reached the maximum clicks". if nb of clicks <4, no button appears.
So I want to use the factory on a Feature test where I'll check that if I create with fatcory girl one object #userdeal1 where the user reached 4 clicks, on this card he sees the button and its text, but for other deals he sees nothing.
Here's what I have so far
models
class Deal < ActiveRecord::Base
has_many :user_deals, dependent: :destroy
has_many :users, through: :user_deals
end
class User < ActiveRecord::Base
has_many :user_deals
has_many :deals, through: :user_deals
end
class UserDeal < ActiveRecord::Base
belongs_to :user, :foreign_key => 'user_id'
belongs_to :deal, :foreign_key => 'deal_id'
end
And the structure of the table user_deal
# Table name: user_deals
#
# id :integer not null, primary key
# user_id :integer
# deal_id :integer
# number_of_clicks :integer default(0)
# created_at :datetime
# updated_at :datetime
So far I found how to create
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
end
Following factorygirl ReadMe I created this:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}#example.com"}
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
factory :superadmin do
after(:create) {|user| user.add_role(:superadmin)}
end
after(:create) do |user|
user.deals << FactoryGirl.create(:deal)
end
factory :user_with_deals do
# used for defining user_deals
transient do
deals_count 5
end
after(:create) do |user, evaluator|
create_list(:deal, evaluator.deals_count, user: user)
end
end
end
end
But now I don't know how to say 'ok of one of these created user_deals in the factory, I'd like one to have number_of_clicks =4, and the other one number_of_clicks=1). so I tried to put this inside the test directly , as below:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
before do
#user = FactoryGirl.create(:user, :user_country_name => 'Germany')
#deal1 = FactoryGirl.build( :deal)
#deal2 = FactoryGirl.build( :deal)
#user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:user => #user,
:deal => #deal1,
:number_of_clicks => 4
).save(validate: false)
#user_deal_with_clicks_available = FactoryGirl.build( :user_with_deals,
:user => #user,
:deal => #deal2,
number_of_clicks => 1 # still has clicks
).save(validate: false)
it "the right behavior for the buttons telling him how many clicks he has left" do
sign_in #user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
But the test does not work and give me different type of errors according to the small change I try. I am pretty sure I don't manage to really create the 2 objects user_deals , one with number_of_clicks= 4 and the other one number_of_clicks= 1.
EDIT
After requests to post the errors:
If i leave inside the #user_deal_with_max_of_clicks " :user=> #user, and :deal => #deal1", I get
NoMethodError:
undefined method `user=' for #<User:0x0000000b4754e0>
and
NoMethodError:
undefined method `deal=' for #<User:0x0000000b451568>
But I remove them out as follows:
#user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:number_of_clicks => 4
).save(validate: false)
then I get this error:
NoMethodError:
undefined method `number_of_clicks=' for #<User:0x0000000b46ce80>
EDIT 2
utilities.rb
include ApplicationHelper
def sign_in(user)
visit new_user_session_path
fill_in I18n.t("formtastic.labels.user.email"), with: user.email
fill_in I18n.t("formtastic.labels.user.password"), with: user.password
click_on I18n.t("devise.sessions.login_page.login")
end
First off, I'd advise you to use let. There is a great guide to write cleaner and better specs.
Secondly, it is vital to set up good factories. They will simplify the testing process enormously:
So starting off, your user factories can be restructured to
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}#example.com" }
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
trait :superadmin do
role :superadmin
end
trait :with_deals do
after(:create) do |user|
create_list(:deal, 5, user: user)
end
end
end
end
Traits are nice way to selectively adjust factories. So if you want your user to be a superadmin, now just use
create(:user, :superadmin)
Want a superadmin with deals? Easy.
create(:user, :superadmin, :with_deals)
You have not posted your deal factories, but I'm sure you can adapt these tricks to them as well.
Finally, leading to the user_deals. In your current factories, you don't address your number_of_clicks column.
Again, you can easily set this up with traits:
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
trait :few_clicks do
number_of_clicks 1
end
trait :many_clicks do
number_of_clicks 4
end
end
end
Now to the spec itself, with the learnt tricks it's an easy task to set up your desired relations:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
let(:user) { create(:user, user_country_name: 'Germany') }
let(:deal1) { create(:deal) }
let(:deal2) { create(:deal) }
before do
create(:user_deal, :few_clicks, user: user, deal: deal1)
create(:user_deal, :many_clicks, user: user, deal: deal2)
end
it "tells the user how many clicks he has left" do
sign_in user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
end
I have updated the code spec code.
What I seek is to destroy a record only with the same user that has created it.
I've tried in the view section and it seems to be working, but the Rspec is throwing me some errors.
Can anyone please tell me how to do a correct spec?
Thanks
My Record model:
class Record < ActiveRecord::Base
#Associations
belongs_to :user
# Validations
validates :user, presence: true
end
My Record factory:
FactoryGirl.define do
factory :record do
user
end
end
My Record controller:
class RecordsController < ApplicationController
before_action :find_record, only: [:show, :edit, :update, :destroy]
before_action :require_permission, only: [:destroy]
def destroy
#record.destroy
flash[:notice] = "The record was deleted successfully"
redirect_to #record
end
private
def require_permission
if current_user != Record.find(params[:id]).user
flash[:notice] = "Permission required"
redirect_to root_path
end
end
end
My record spec:
require 'rails_helper'
describe RecordsController do
let(:record) { create(:record) }
let(:user) { create(:user) }
describe "#destroy" do
let!(:record) { create(:record) }
#UPDATED
login_user
it "deletes the record" do
expect {
delete :destroy, id: record.id, :record => {:user => record.user}
}.to change(Record, :count).by(-1)
expect(flash[:notice]).to eq("The record was deleted successfully")
end
end
end
UPDATE 2
rails_helper.rb
require 'spec_helper'
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
end
at spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
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
My errors:
#destroy
deletes the record (FAILED - 1)
Failures:
1) RecordsController#destroy deletes the record
Failure/Error: expect {
expected #count to have changed by -1, but was changed by 0
you use #record but you let record that's why in error #record is nil
it "deletes the record" do
expect {
delete :destroy, id: record.id, :record => {:user => record.user}
}.to change(Record, :count).by(-1)
expect(flash[:notice]).to eq("The record was deleted successfully")
end
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.
I think I know what the problem is here, but I can't seem to figure out how to fix it.
here are my models
User
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
has_many :student_groups
...
end
StudentGroup
class StudentGroup < ActiveRecord::Base
attr_accessible :name
belongs_to :user
has_many :subjects
has_many :students
end
Subject
class Subject < ActiveRecord::Base
attr_accessible :end_date, :name
belongs_to :student_group
belongs_to :student
end
Student
class Student < ActiveRecord::Base
attr_accessible :gender, :name
belongs_to :student_group
has_many :subjects
end
in my Student_Spec.rb I have the following test EDITED:
...
before(:each) do
#user = Factory(:user)
#student_group_attr = { name: "4a"}
#student_group = #user.student_groups.create(#student_group_attr)
#date = Date.today+180
#subject_attr = { name: "English", end_date: #date}
end
...
describe "Student associations" do
before(:each) do
#subject = #student_group.subjects.create!(#subject_attr)
#student_attr = { name: "Example Student", gender: "Female" }
#student = #student_group.students.create(#student_attr)
end
it "should have the right associated student" do
#subject.student_id.should == #student.id
#subject.student.should == #student
end
end
I have the same test in other specs and it works fine - I checked it out in the console and got this:
2.0.0-p0 :015 > #subject
=> #<Subject id: 1, name: "English", student_group_id: 1, student_id: nil, end_date: "2013-11-18", created_at: "2013-05-22 15:08:44", updated_at: "2013-05-22 15:08:44">
So for whatever reason, the student_id isn't getting linked to the subject...what am I doing wrong here? Thanks!
Reload #subject, maybe its not loaded from the db and therefore its empty
Figured it out.
Changed models to the following:
student_groups.rb
class StudentGroup < ActiveRecord::Base
attr_accessible :name
belongs_to :user
has_many :students
end
students.rb
class Student < ActiveRecord::Base
attr_accessible :gender, :name
belongs_to :student_group
has_many :subjects
end
subject.rb
class Subject < ActiveRecord::Base
attr_accessible :end_date, :name
belongs_to :student
end
And model specs to the following:
student_group_spec.rb
require 'spec_helper'
describe StudentGroup do
before(:each) do
association_attr
end
it "should create a new instance with valid attributes" do
#user.student_groups.create!(#attr).should be_valid
end
describe "User associations" do
it "should have a user attribute" do
#student_group.should respond_to(:user)
end
it "should have the right associated user" do
#student_group.user_id.should == #user.id
#student_group.user.should == #user
end
end
describe "Student associations" do
it "should have a student attritube" do
#student_group.should respond_to(:students)
end
end
end
student_spec.rb
require 'spec_helper'
describe Student do
before(:each) do
association_attr
end
it "should create a new instance with valid attributes" do
#student_group.students.create!(#attr).should be_valid
end
describe "Student_Group associations" do
it "should have a student_group attribute" do
#student.should respond_to(:student_group)
end
it "should have the right associated student_group" do
#student.student_group_id.should == #student_group.id
#student.student_group.should == #student_group
end
end
describe "Subject associations" do
it "should have a subject attribute" do
#student.should respond_to(:subjects)
end
end
end
subject_spec.rb
require 'spec_helper'
describe Subject do
before(:each) do
association_attr
end
it "should create a new instance with valid attributes" do
#student.subjects.create!(#subject_attr).should be_valid
end
describe "Student associations" do
it "should have a student attribute" do
#subject.should respond_to(:student)
end
it "should have the right associated student" do
#subject.student_id.should == #student.id
#subject.student.should == #student
end
end
end
And finally changed spec_helper.rb as follows:
def association_attr
# User attritbutes
#user = Factory(:user)
# Student_group
#student_group = #user.student_groups.create(#student_group_attr)
# Student_group attributes
#student_group_attr = { name: "4a"}
# Student
#student = #student_group.students.create(#student_attr)
# Student attributes
#student_attr = { name: "Example Student", gender: "Transgender" }
# Subject
#subject = #student.subjects.create!(#subject_attr)
# Subject attributes
#subject_attr = { name: "English", end_date: #date}
#date = Date.today+180
end
Thanks to Frederick Cheung and Billy Chan for the comments.