RSpec testing with followerships / friendships has many through users - ruby-on-rails

I inspired myself with the following link, http://railscasts.com/episodes/163-self-referential-association, but the rspec testing is not coming easy.
user model:
class User < ActiveRecord::Base
# Associations
has_many :followerships
has_many :followers, :through => :followerships
has_many :inverse_followerships, :class_name => "Followership", :foreign_key => "follower_id"
has_many :inverse_followers, :through => :inverse_followerships, :source => :user
end
followership model:
class Followership < ActiveRecord::Base
belongs_to :user
belongs_to :follower, :class_name => "User"
end
followerhip factory:
FactoryGirl.define do
factory :followership do
user_id 1
follower_id 1
end
end
followerships controller:
class FollowershipsController < InheritedResources::Base
def create
#followership = current_user.followerships.build(:follower_id => params[:follower_id])
if #followership.save
flash[:notice] = "Following."
redirect_to root_url
else
flash[:error] = "Unable to follow."
redirect_to root_url
end
end
def destroy
#followership = current_user.followerships.find(params[:id])
#followership.destroy
flash[:notice] = "Removed followership."
redirect_to current_user
end
end
folowerships controller spec (this is all wrong):
require 'rails_helper'
describe FollowershipsController do
let!(:followership) { create(:followership) }
let!(:follower) { followership.follower }
let!(:user) { create(:user) }
before do
sign_in :user, user
end
describe "#create" do
it "saves the followership" do
post :create, followership: { follower_id: follower }
expect(response).to redirect_to(root_path)
expect(assigns(:followership).followership.followers).to eq(user)
expect(flash[:notice]).to eq("Following.")
end
it "fails to save followership" do
expect(post :create, followership: { follower_id: follower }).to redirect_to(root_path)
expect(flash[:notice]).to eq("Unable to follow.")
end
end
describe "#destroy" do
it "deletes the followership" do
expect {
delete :destroy, id: follower
}.to change(Followership, :count).by(-1)
expect(flash[:notice]).to eq("Removed followership.")
end
end
end
Error from followerships controller Rspec
FollowershipsController
#destroy
deletes the followership (FAILED - 1)
#create
saves the followership (FAILED - 2)
fails to save followership (FAILED - 3)
Failures:
1) FollowershipsController#destroy deletes the followership
Failure/Error: delete :destroy, id: follower
ActionController::UrlGenerationError:
No route matches {:action=>"destroy", :controller=>"followerships", :id=>nil}
2) FollowershipsController#create saves the followership
Failure/Error: expect(assigns(:followership).followership.followers).to eq(user)
NoMethodError:
undefined method `followership' for #<Followership:0x00000109f69780>
3) FollowershipsController#create fails to save followership
Failure/Error: expect(flash[:notice]).to eq("Unable to follow.")
expected: "Unable to follow."
got: "Following."
(compared using ==)
Thanks for the help :)

The let command uses lazy evaluation, so these records are not actually created until called. Use the let! syntax to ensure they're created before your tests run:
let!(:followership) { create(:followership) }
let!(:follower) { followership.follower }
let!(:user) { create(:user) }
Make sure your validations also only allow creation of a following if it doesn't already exist for that pair of users:
class Followership < ActiveRecord::Base
validates_uniqueness_of :user_id, scope: :follower_id
Also, it's not guaranteed that the follower/followership relationships will belong to user since user doesn't necessarily have an id of 1.
Finally, assigns is a method, so the syntax should be assigns(:followership) not assigns[:followership]

Related

Rails RSpec test - prevent delete comment for user who is not the author of it

I'm trying to test the 'destroy' action for my nested comments controller.
In my filmweb app I have scope and validations which prevents users from deleting a comment which is not the author. In web version everything works well but I don't know how to test this case.
Here is my comments_controller
def destroy
#comment = #movie.comments.find(params[:id])
if #comment.destroy
flash[:notice] = 'Comment successfully deleted'
else
flash[:alert] = 'You are not the author of this comment'
end
redirect_to #movie
end
Comment model
class Comment < ApplicationRecord
belongs_to :user
belongs_to :movie
validates :body, presence: true
validates :user, :movie, presence: true
validates :user, uniqueness: { scope: :movie }
scope :persisted, -> { where.not(id: nil) }
end
User model has_many :comments, dependent: :destroy Movie model has_many :comments, dependent: :destroy .
I'm using devise and FactoryBot, specs are here:
describe "DELETE #destroy" do
let(:user) { FactoryBot.create(:user) }
let(:movie) { FactoryBot.create(:movie) }
let(:other_user) { FactoryBot.create(:user, user_id: 100)}
it "doesn't delete comment" do
sign_in(other_user)
comment = FactoryBot.create(:comment, movie: movie, user: user)
expect do
delete :destroy, params: { id: comment.id, movie_id: movie.id }
end.to_not change(Comment, :count)
expect(flash[:alert]).to eq "You are not the author of this comment"
end
end
I've got an error undefined method `user_id=' for #<User:0x00007fb049644d20> and no idea what is the good way to do so.
===EDIT===
Here is my FactoryBot
FactoryBot.define do
factory :user do
email { Faker::Internet.email }
password "password"
confirmed_at 1.day.ago
end
factory :unconfirmed_user do
email { Faker::Internet.email }
password "password"
end
end
The problem is that the the users table does not have a user_id column which you are trying to use in the other_user instance, the column name is simply id:
let(:other_user) { FactoryBot.create :user, id: 100 }
You can leave out the id entirely, it will get a different id automatically:
let(:other_user) { FactoryBot.create :user }

Rspec undefined method build for #<Array:xxxx>

I am getting this error message from Rspec
Failure/Error: #address = #owner.addresses.build
NoMethodError:
undefined method `build' for #<Array:0x007f9faba657f0>
Below is snippet from my controller
class AddressesController < ApplicationController
def new
#owner = Owner.find(params[:owner_id])
#address = #owner.addresses.build
end
end
Below is snippet from my spec file:
describe "GET #new" do
let(:owner) { create(:owner) }
before { xhr :get, :new, owner_id: owner.id }
it 'response will be success' do
expect(response).to be_success
end
end
Below is my route for addresses#new
new_owner_address GET /owners/:owner_id/addresses/new(.:format) addresses#new
addresses.build method is working fine in console and working fine in my application but it is failing on spec. Any suggestion will be highly appreciated.
Update:
factory:
FactoryGirl.define do
factory :owner do
name 'foo'
address 'bar'
amount 200.00
country 'foobar'
state 'qax'
end
end
Model and Associations:
class Owner < ActiveRecord::Base
has_many :addresses, dependent: :destroy
end
class Address < ActiveRecord::Base
belongs_to :owner
end
#owner.addresses has to be an ActiveRecord::Relation in order to respond to #build. However, it seems to return an Array in your spec. Does your factory override the default association somehow?

Rspec (for Michael Hartl example) for the definition of relationship following users

I'm trying to make a spec test for the MHartl's example.
As it follows bellow my difficult is for #Follows a user.
class User < ActiveRecord::Base
# Associations
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
# Follows a user.
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
# Unfollows a user.
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
For my spec:
require 'rails_helper'
RSpec.describe User, :type => :model do
let(:user) { build(:user) }
describe 'Validations' do
it 'has a valid factory' do
expect(user).to be_valid
end
it { should validate_presence_of(:email) }
it { should validate_presence_of(:password) }
it { should validate_confirmation_of(:password) }
end
let(:user) { create(:user) }
let(:other_user) { create(:user) }
describe '#following?' do
it "expect relationship between two users to be empty" do
expect(user.active_relationships).to be_empty
end
end
describe '#follow' do
it "creates the active relationship between two users" do
user.follow(other_user)
expect(user.active_relationships.first.followed_id).to eq(other_user.id)
end
it "creates the passive relationship between two users" do
user.follow(other_user)
expect(other_user.passive_relationships.first.follower_id).to eq(user.id)
end
end
describe '#unfollow' do
it "destroys the active relationship between two users" do
user.follow(other_user)
user.unfollow(other_user)
expect(user.active_relationships.find_by.followed_id).to change(Relationship, :count).by(-1)
end
end
My failures :
1) User#unfollow destroys the active relationship between two users
Failure/Error: active_relationships.find_by(followed_id: other_user.id).destroy
NoMethodError:
undefined method `destroy' for nil:NilClass
2) User#follow creates the passive relationship between two users
Failure/Error: expect(other_user.passive_relationships.first.follower_id).to eq(user.id)
NoMethodError:
undefined method `passive_relationships' for #<User:0x0000010c4146e8>
If you need more info about this post, please fell free to ask!
and please tell me were can I learn more about this specs tests.
thanks for your help :)
You're missing the :passive_relationships relationship in the User model that mirrors the :active_relationships relationship:
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
May be better to create this other user in let, and replace build with create, since you are not validation a new unsaved record:
let(:user) { create(:user) }
let(:other_user) { create(:user) }
then
user.follow(other_user)

how to handle undefined methods during minitest controller testing

I have following methods in my contest model.
class Contest < ActiveRecord::Base
has_many :submissions
has_many :users, through: :submissions
validates_presence_of :name, :admin_id
acts_as_votable
def admin_name
User.find_by_id(self.admin_id).username
end
def tonnage
self.submissions.sum(:tonnage)
end
def contest_type_tr
I18n.t("contests.contest_type")[self.contest_type]
end
def contest_short_descr
I18n.t("contests.contest_short_descr")[self.contest_type]
end
end
When doing a test for the contest controller I get the following error:
ActionView::Template::Error: undefined method `username' for nil:NilClass
Why is this and how can I fix it?
My specs (minitest) are available below.
require "test_helper"
describe ContestsController do
let(:user) { users :default }
let(:contest) { contests :one }
it "gets index" do
get :index
value(response).must_be :success?
value(assigns(:contests)).wont_be :nil?
end
it "gets new" do
get :new
value(response).must_be :success?
end
it "creates contest" do
expect {
post :create, contest: { }
}.must_change "Contest.count"
must_redirect_to contest_path(assigns(:contest))
end
it "shows contest" do
get :show, id: contest
value(response).must_be :success?
end
it "gets edit" do
get :edit, id: contest
value(response).must_be :success?
end
it "updates contest" do
put :update, id: contest, contest: { }
must_redirect_to contest_path(assigns(:contest))
end
it "destroys contest" do
expect {
delete :destroy, id: contest
}.must_change "Contest.count", -1
must_redirect_to contests_path
end
end

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

Resources