Rspec and rails mailers throws nil for nested attributes - ruby-on-rails

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

Related

rspec telling me that ".update' method is undefined

This is the topic resolver. I'm trying to update a field in learning_path table each time this query is executed for a student.
class Resolvers::Topic < GraphQL::Schema::Resolver
type Types::TopicType, null: true
description 'Returns the topic information'
argument :title, String, required: true
def resolve(title:)
user = context[:current_user]
ability = Ability.for(user)
topic = Topic.find_by(title: title)
if %w[student teacher admin].include?(user&.role) && ability.can?(:read, Topic)
if user&.role == 'student'
user.userable.learning_paths.find_by(subject_id: topic.subject_id).update(visited_at: DateTime.now)
end
if %w[student teacher].include?(user&.role) && !user&.userable&.subjects&.include?(topic.subject)
raise GraphQL::ExecutionError, 'This topic is not avaiable for you.'
end
topic
else
raise GraphQL::ExecutionError, 'You can't access this information.'
end
end
end
It works but the rspec is failling with this error:
1) LmsApiSchema topic when there's a current user returns an error if the student is not subscribe
Failure/Error: user.userable.learning_paths.find_by(subject_id: topic.subject_id).update(visited_at: DateTime.now)
NoMethodError:
undefined method `update' for nil:NilClass
topic rspec test that fails in testing:
context 'when there\'s a current user' do
let!(:student) { create(:student) }
let!(:user) { create(:user, userable: student) }
let!(:subject_a) { create(:subject) }
let!(:topic) { create(:topic, subject_id: subject_a.id) }
let!(:question) { create(:question, topic_id: topic.id) }
before do
create(:option, question_id: question.id)
prepare_context({ current_user: user })
end
it 'returns an error if the student is not subscribe' do
expect(graphql!['errors'][0]['message']).to eq('This topic is not avaiable for you.')
end
Just worked out. Added the & before setting the method for avoiding the nil error.
Figured it out thanks to this
user.userable.learning_paths.find_by(subject_id: topic.subject_id)&.update(visited_at: DateTime.now)

rspec testing instance methods

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

Rspec testing a controller using from to?

Tested in browser and works fine. Test error says "expected result to have changed from 0 to 1, but did not change". Is this a factory issue or rspec issue? Why is it not changing?
Error:
Failures:
1) ShortLinksController Short links controller Clicking a short link increments the click counter by 1
Failure/Error: expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
expected result to have changed from 0 to 1, but did not change
# ./spec/controllers/short_links_controller_spec.rb:34:in `block (4 levels) in <top (required)>'
Rspec:
it "increments the click counter by 1" do
short_link = create(:short_link)
expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
end
Controller:
def url_dispatch
id = params[:id]
record = ShortLink.where(["short_link = ?", id]).first
if record.update(click_counter: record.click_counter + 1)
redirect_to record.redirect_to
else
render '/not_found'
end
end
Factory:
FactoryGirl.define do
factory :short_link do
redirect_to "http://google.com"
title "This is the google page"
short_link "xGh7u"
click_counter 0
owner Owner.create!(first_name: "Bob", last_name: "Diller", email: "bdiller#example.com")
end
end
per Fab's request, here is how I'm currently working around the issue.
context 'save invocations' do
before(:each) do
#org = create(:organization)
user = create(:user, organization: #org, is_admin: true)
sign_in user
end
it 'valid scenario' do
user2 = create(:user, organization: #org, is_admin: false)
put :update, id: user2, user: { is_admin: true }
user2.reload
expect(response).to have_http_status(204)
expect(user2.is_admin).to eq true
end
end
Here I'm calling user2.reload in order to get the updated attributes from the user2 factory.
I don't know why the expect{} syntax doesn't work for factories but you could refactor your code like this:
it "increments the click counter by 1" do
short_link = create(:short_link)
count = short_link.click_counter
get :url_dispatch, { id: short_link.short_link }
short_link.reload
expect(short_link.click_counter).to eq count + 1
end
Again I'm not saying this is best practice, I just couldn't find anything in the FactoryGirl documentation regarding RSpec 3 expect syntax in controllers that update attributes.

How to Test a Class Method in Rspec that has a Has Many Through Association

How would I test the class method .trending in Rspec considering that it has a has many through association. .trending works but it currently has not been properly vetted in Rspec. Any advice?
class Author < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
validates :name, presence: true
validate :name_length
def self.trending
hash = {}
all.each{|x|
hash[x.id] = x.comments.where("comments.created_at >= ?", Time.zone.now - 7.days).count
}
new_hash = hash.sort_by {|k,v| v}.reverse!.to_h
new_hash.delete_if {|k, v| v < 1 }
new_hash.map do |k,v,|
self.find(k)
end
end
private
def name_length
unless name.nil?
if name.length < 2
errors.add(:name, 'must be longer than 1 character')
end
end
end
end
Test I attempted to use (it didn't work)
describe ".trending" do
it "an instance of Author should be able to return trending" do
#author = FactoryGirl.build(:author, name:'drew', created_at: Time.now - 11.years, id: 1)
#post = #author.posts.build(id: 1, body:'hello', subject:'hello agains', created_at: Time.now - 10.years)
#comment1 = #post.comments.build(id: 1, body: 'this is the body', created_at: Time.now - 9.years)
#comment2 = #post.comments.build(id: 2, body: 'this was the body', created_at: Time.now - 8.years)
#comment3 = #post.comments.build(id: 3, body: 'this shall be the body', created_at: Time.now - 7.minutes)
Author.trending.should include(#comment3)
end
end
Neither FactoryGirl.build nor ActiveRecord::Relation#build persists a record to the database—they just return an un-saved instance of the object—but Author.trending is looking for records in the database. You should either call save on the instances to persist them to the database, or use create instead of build.

How to stub after_create callback save! in model?

I receive following error:
Output:
1) LabelsController#create label is new creates a new label
Failure/Error: post :create, attributes[:label], format: :json
NoMethodError:
undefined method `save!' for nil:NilClass
# ./app/models/labeling.rb:17:in `update_target'
In Labeling model:
after_create :update_target
def update_target
self.target.save!
end
Test:
require 'spec_helper'
describe LabelsController do
before(:each) do
controller.stub(:current_user).and_return(mock_model(User))
stub_request(:any, "www.example.com").to_return(status: 200)
end
describe "#create" do
context "label is new" do
it "creates a new label" do
attributes = {
label: {
name: "test",
labeling: {
target_type: "Link", target_id: 1
}
}
}
response.status.should == 200
post :create, attributes[:label], format: :json
end
end
end
end
Labeling controller:
def create
label = Label.find_by_name(params[:name])
labeling = label.labelings.build do |lb|
lb.user_id = current_user.id
lb.target_type = params[:labeling][:target_type]
lb.target_id = params[:labeling][:target_id]
end
if labeling.save
render json: {
name: label.name,
id: label.id,
labeling: {
id: labeling.id
}
}
end
end
By the looks of it you don't have a Target with ID 1 on the database, so where you refer to self.target the returned value is nil.
What I'd do in your case is first create a target and then pass its id to the attributes hash:
target = Traget.create!
attributes = {
label: {
name: "test",
labeling: {
target_type: "Link", target_id: target.id
}
}
}
This way you don't need to stub anything.
If you really must stub the method you can use RSpecs any_instance method:
Labeling.any_instance.stub(:update_target).and_return(true)

Resources