How can I test these instance methods with rspec and factory?
factories
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "example#{n}#gmail.com"
password 'example0000'
password_confirmation 'example0000'
new_chat_notification { Faker::Number.between(0, 10) }
new_other_notification { Faker::Number.between(0, 10) }
end
factory :notification do
notifiable_id { Faker::Number.between(1, 10) }
notifiable_type { Faker::Lorem.word }
action { Faker::Hipster.word }
checked_at { Faker::Time.between(DateTime.now - 2, DateTime.now - 3) }
association :sender, factory: :user
association :recipient, factory: :user
end
end
user.rb
#check and decrease chat notification that happens between 2 given users (max 1)
def decreasing_chat_notification_number(user)
notification = self.notifications.between_chat_recipient(user).unchecked.first
self.checking_and_decreasing_notification(notification) if notification.present?
end
#check and decrease task notifications that happens between 2 given users
def decreasing_task_notification_number(user)
self.notifications.task.between_other_recipient(user).unchecked.each do |notification|
self.checking_and_decreasing_notification(notification)
end
end
UPDATE
user.rb (here is the method that is called)
def checking_and_decreasing_notification(notification)
notification.check_notification
if notification.notifiable_type == "Message"
decrease_new_chat_notifications
decreased_chat_number_pusher
else
decrease_new_other_notifications
decreased_other_number_pusher
end
end
user_spec.rb
let(:sender) { create(:user) }
let(:user) { create(:user) }
let(:notification) { create(:notification, notifiable_type: "Message", recipient: user, sender: sender) }
it "decreasing_chat_notification_number" do
allow(user).to receive(:checking_and_decreasing_notification).with(notification)
user.decreasing_chat_notification_number(sender)
expect(user).to receive(:checking_and_decreasing_notification).with(notification)
end
error message:
1) User instance methods decreasing_chat_notification_number
Failure/Error: expect(user).to receive(:checking_and_decreasing_notification).with(notification)
(#<User:0x007fefcfd6ce20>).checking_and_decreasing_notification(#<Notification id: 1,
recipient_id: 1, created_at: "2016-04-14 19:47:36", updated_at: "2016-04-14 19:47:36",
sender_id: 2, checked_at: "2016-04-12 02:32:50", notifiable_id: 4, notifiable_type: "Message", action: "tilde">)
expected: 1 time with arguments: (#<Notification id: 1,
recipient_id: 1, created_at: "2016-04-14 19:47:36", updated_at: "2016-04-14 19:47:36",
sender_id: 2, checked_at: "2016-04-12 02:32:50", notifiable_id: 4, notifiable_type: "Message", action: "tilde">)
received: 0 times
(sidenote) You do not need self in your methods.
Take a look:
def decreasing_chat_notification_number(user)
notification = notifications.between_chat_recipient(user).unchecked.first
checking_and_decreasing_notification(notification) if notification.present?
end
describe '#decreasing_chat_notification_number' do
let(:notification) { create(:notification) }
let(:user) { create(:user) }
subject { create(:user) }
it 'your description' do
expect(subject).to receive(:checking_and_decreasing_notification).with(notification)
subject.decreasing_chat_notification_number(user)
end
end
Related
I have a method in my model that group items by variant id if multiple items have the same variant_id she merge them and add their quantity.
My model:
class ShoppingCart < ApplicationRecord
belongs_to :company
belongs_to :user
has_many :items, class_name: "ShoppingCartItem", dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |attributes| attributes['quantity'].blank? }
before_validation :remove_invalid_items
before_validation :merge_items
def merge_items
self.items = items.group_by { |i| i[:variant_id] }.map do |variant_id, is|
quantity_sum = is.sum { |i| i[:quantity] }
ShoppingCartItem.new(variant_id: variant_id, quantity: quantity_sum)
end
end
end
This method works well when i try it manually but in my tests rspec seems to ignore this method
My tests:
require 'rails_helper'
RSpec.describe ShoppingCart, type: :model do
describe "associations" do
it { is_expected.to belong_to(:company) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:items) }
end
describe "merge_items" do
let(:shopping_cart) { create(:shopping_cart) }
it "merge items if same variant_id" do
existing_item = create(:shopping_cart_item, shopping_cart: shopping_cart, variant_id: "same variant_id", quantity: 1)
item = create(:shopping_cart_item, shopping_cart: shopping_cart, variant_id: "same variant_id", quantity: 1)
expect(shopping_cart.reload.items.count).to eq(1)
end
it "not merge items if variant_id different" do
existing_item = create(:shopping_cart_item, shopping_cart: shopping_cart, variant_id: "variant_id", quantity: 1)
item = create(:shopping_cart_item, shopping_cart: shopping_cart, variant_id: "different variant_id", quantity: 1)
expect(shopping_cart.reload.items.count).to eq(2)
end
end
end
Tests output:
Failure/Error: expect(shopping_cart.reload.items.count).to eq(1)
expected: 1
got: 2
I changed the code and this works:
require 'rails_helper'
RSpec.describe ShoppingCart, type: :model do
describe "associations" do
it { is_expected.to belong_to(:company) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:items) }
end
describe "merge_items" do
let(:shopping_cart) { create(:shopping_cart) }
context "same variant_id" do
let(:params) {{ shopping_cart: {items_attributes: [{variant_id: 'same variant_id', quantity: 2}, {variant_id: 'same variant_id', quantity: 2}]}}}
before do
shopping_cart.update params[:shopping_cart]
end
it "create just one item" do
expect(shopping_cart.reload.items.count).to eq(1)
end
it "adds all quantities" do
expect(shopping_cart.reload.items.last.quantity).to eq(4)
end
end
context "not same variant_id" do
let(:params) {{ shopping_cart: {items_attributes: [{variant_id: 'variant_id', quantity: 2}, {variant_id: 'different variant_id', quantity: 2}]}}}
before do
shopping_cart.update params[:shopping_cart]
end
it "not merge items if variant_id different" do
expect(shopping_cart.reload.items.count).to eq(2)
end
end
end
end
I'm trying to test a Mailer but I can't figure why I'm not reaching a nested model attribute in mailer
let's see, I have this models
class CandidateChallenge < ApplicationRecord
has_one :candidate_evaluation
def coach
self.candidate_evaluation.user
end
end
class CandidateEvaluation < ApplicationRecord
belongs_to :candidate_challenge
end
In mailer I have
def notify_coach(candidate_challenge_id)
#candidate_challenge = CandidateChallenge.find(candidate_challenge_id)
#user = #candidate_challenge.user
#coach = #candidate_challenge.coach # <= throws nil
mail(
to: #coach.email, # <= this is nil too
cc: #user.email,
)
end
And in my test I have
let!(:user) { create(:coach_user) }
let!(:candidate_challenge) {
create(:candidate_challenge, user: user)
}
let(:job) { NotificationMailer.new }
# Test passing
it "should have a coach" do
expect(candidate_challenge.coach.email).to eq(candidate_challenge.candidate_evaluation.user.email)
end
# Test failing
it "job is created" do
ActiveJob::Base.queue_adapter = :test
expect {
job.notify_coach(candidate_challenge.id).deliver_now
}.to change {
ActiveJob::Base.queue_adapter.enqueued_jobs.size
}.by_at_least(1)
end
Error
Failure/Error: to: #coach.email,
NoMethodError:
undefined method `email' for nil:NilClass
With pry
asking from model throws nil
CandidateChallenge.last.candidate_evaluation
=> #<CandidateEvaluation:0x0000000111b25ab8
id: 25,
candidate_challenge_id: 13,
user_id: nil>
CandidateChallenge.find(candidate_challenge.id).candidate_evaluation
=> #<CandidateEvaluation:0x000000012223a668
id: 25,
candidate_challenge_id: 13,
user_id: nil>
from factorybot gives expected result
candidate_challenge.coach.email
=> "test26#example.com"
candidate_challenge.candidate_evaluation.user
=> #<User id: 26, email: "test26#example.com" ...>
candidate_challenge.candidate_evaluation
=> #<CandidateEvaluation:0x00000001218357f8
id: 26,
candidate_challenge_id: 13,
user_id: 26>
So, I'm wondering why if I ask in mailer from class model CandidateChallenge.find(candidate_challenge_id) user_id is nil?, how can I test notify_coach method with rspec without losing this relationship?
EDIT:
I forgot to mention that let!(:user) { create(:user) } is a coach user
I'm using this factory file for user model:
FactoryBot.define do
factory :user do |f|
f.sequence(:first_name) { |n| "#{Faker::Name.first_name}foo#{n}" }
f.sequence(:last_name) { |n| "#{Faker::Name.last_name}foo#{n}" }
f.sequence(:email) { |n| "foo#{n}#example.com" }
f.password "foobar"
f.password_confirmation { |u| u.password }
f.sequence(:confirmed_at) { Date.today }
f.sequence(:telephone_number) { Faker::Number.number(10) }
f.sequence(:mobile_phone_number) { Faker::Number.number(10) }
f.sequence(:verification_code) { '0000' }
f.sequence(:is_verified) { false }
end
end
and Order.rb factory is:
FactoryBot.define do
factory :order do
association :store
association :user
total_price Faker::Number.positive
total_discount Faker::Number.positive
end
end
And the order model should have these three FKs, two of which are from User:
class Order < ApplicationRecord
belongs_to :customer, class_name: 'User'
belongs_to :carrier, class_name: 'User'
belongs_to :store
end
and in order_controllers_spec.rb file, I got these:
let(:customer) { FactoryBot.create(:user) }
let(:carrier) { FactoryBot.create(:user) }
let(:store) { FactoryBot.create(:store) }
let(:order) { FactoryBot.create(:order, customer_id: customer.id, carrier_id: carrier.id, store_id: store.id) }
Each time I run the show test,
describe "GET show" do
it 'has a 200 status code' do
get :show, params: { id: order_item.id }
expect(response.status).to eq(200)
end
end
I got this error
Failure/Error: let(:order) { FactoryBot.create(:order, customer_id: customer.id, carrier_id: carrier.id, store_id: store.id) }
NoMethodError:
undefined method `user=' for #<Order:0x00007fcd2efc5118>
Any ideas about how to solve this?
I think in your Order's factory definition you're using user, instead of customer or carrier as your Order model define.
association :customer, factory: :user
association :carrier, factory: :user
I have User (which is I used Devise) and Profile model where User has_one Profile as their relationship. I got an error when run the rspec test. Below is my spec to handle when user is updating his/her profile.
spec/controller/profiles_controller_spec.rb
RSpec.describe ProfilesController, type: :controller do
let(:profile) { FactoryGirl.create(:profile) }
let (:valid_attributes) { FactoryGirl.attributes_for(:profile) }
let (:invalid_attributes) { FactoryGirl.attributes_for(:profile).merge({fname: nil}) }
let(:user) { FactoryGirl.create(:user) }
let(:valid_session) { sign_in(user) }
describe "PUT #update" do
before { valid_session }
context "with valid attributes" do
it "saves valid profile" do
expect do
put :update, { id: profile.id, profile: { fname: "Cena" } }
end.to change{ profile.reload.fname }.to("Cena")
end
end
spec/factories/profiles.rb
FactoryGirl.define do
factory :profile do
user
fname "John"
lname "Doe"
avatar "my_avatar"
end
end
app/controller/profiles_controller.rb
class ProfilesController < ApplicationController
private
def user_params
params.require(:user).permit(:id, :login, :email,
profile_attributes: [
:id, :user_id, :fname, :lname, :avatar, :avatar_cache
])
end
end
And here is the error when run rspec spec/controllers/accounts_controller_spec.rb
Failures:
1) AccountsController PUT #update with valid attributes saves valid profile
Failure/Error: put :update, {id: profile.id, user_id: user.id, profile: { fname: "Cena" }}
ActionController::ParameterMissing:
param is missing or the value is empty: user
let(:user) { FactoryGirl.create(:user) }
let(:profile) { FactoryGirl.create(:profile, user_id: user.id ) }
describe "PUT #update" do
before { valid_session }
context "with valid attributes" do
it "saves valid profile" do
expect do
put :update, id: user.id, user: { profile_attributes: { user_id: user.id, fname: "Cena" } }
end.to change{ profile.reload.fname }.to("Cena")
end
end
end
The profiles_controller.rb code you posted is missing the update action (and also the class name is AccountController), but I guess you are doing something like user.update(user_params).
If that's the case, as the error says, the params passed from the controller spec does not have :user key, and that is causing the error.
Assuming from the #user_params method and the error, the params passed to the post in controller spec needs to look like the following:
{
user: {
id: xxx, ...,
profile_attributes: {
id: xxx,
fname: xxx, ...
}
}
}
Here is the method I am testing:
class User < ActiveRecord::Base
has_many :sports, :through => :user_sports, order: "user_sports.created_at", class_name: "Sport"
has_many :user_sports
def primary_sport
return nil if user_sports.blank?
user_sports.primary_only.first.sport
end
end
User Factory;
FactoryGirl.define do
sequence(:email) do |n|
"user#{n}#example.com"
end
factory :user do
email
first_name Faker::Name.first_name
last_name Faker::Name.last_name
password "password"
password_confirmation "password"
agreed_to_age_requirements true
username "testing123"
state "AL"
city_id 201
school_id 20935
handedness "Left"
customer_id { "#{rand(1000)}" }
sports {[create(:sport)]}
after(:create) do |user, elevator|
user.subscriptions << create(:subscription)
user.roles << create(:role)
end
end
factory :athlete, class: "Athlete", parent: :user do
type "Athlete"
recruit_year "2016"
end
end
Here is my test:
require 'spec_helper'
describe User do
describe "associations" do
it { should have_and_belong_to_many(:roles) }
it { should belong_to(:account_type) }
it { should belong_to(:primary_sport).class_name("Sport") }
it { should belong_to(:school) }
it { should belong_to(:city) }
it { should belong_to(:hometown) }
it { should have_many(:social_actions) }
it { should have_one(:invitation) }
it { should have_many(:authorizations) }
it { should belong_to(:user_type) }
it { should have_and_belong_to_many(:positions).class_name "SportPosition" }
it { should have_many(:sports).through(:user_sports) }
it { should have_many(:user_sports) }
it { should have_many :contributorships }
it { should have_many(:managed_athletes).through(:contributorships) }
it { should have_and_belong_to_many(:subscriptions) }
end
describe "nested attributes" do
it { should accept_nested_attributes_for(:user_sports) }
it { should accept_nested_attributes_for(:subscriptions) }
end
describe "validations" do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should allow_value("test#test.com").for(:email) }
it { should_not allow_value("test.com").for(:email) }
end
describe "instance methods" do
before :each do
#user = create(:user, sports: [])
#school_admin_role = create(:role, name: "School Admin")
#contributor_role = create(:role, name: "Contributor")
end
describe "#my_athletes_path" do
it "returns a school admin path if the user has the role of School Admin" do
#user.roles << #school_admin_role
#user.my_athletes_path.should eq school_admin_athletes_path
end
it "returns a school admin path if the user has the role of Contributor" do
#user.roles << #contributor_role
#user.my_athletes_path.should eq contributor_dashboard_path
end
it "returns nil if the user has no Contributor or School Admin role" do
#user.my_athletes_path.should be_nil
end
end
describe "#first_time_login?" do
it "will evalute true if the user has logged in only once" do
#user.sign_in_count = 1
#user.save
#user.first_time_login?.should be_true
end
end
describe "#confirmation_required?" do
it "returns false" do
#user.confirmation_required?.should be_false
end
end
describe "#primary_sport", focus: true do
context "when user has no primary sport" do
it "returns nil" do
#user.primary_sport.should be_nil
end
end
context "when user has a primary sport" do
it "returns sport object" do
#user.sports << create(:sport)
#user.primary_sport.should eq #user.sports.first
end
end
end
end
end
This is the error I am receiving:
Failure/Error: #user.primary_sport.should eq #user.sports.first
NoMethodError:
undefined method sport for nil:NilClass
This is because when the user_sport association is created in the User Factory, the primary column is being set to false. Not sure how to fix this. Any help is greatly appreciated! Also, sorry for the ignorance on the TDD front, Im a newb
Couldn't you just add the following to your after(:create) block in the User factory:
us = user.user_sports.first
us.primary = true
us.save
That would ensure the association gets the primary flag.