rspec telling me that ".update' method is undefined - ruby-on-rails

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)

Related

Rspec and rails mailers throws nil for nested attributes

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

rails rspec error expected string got nil?

I am new to testing in rails, I am trying to pass the test as below where it kept throwing back errors as
/scooties_coupon POST Coupon /scooties_coupons/:coupon_id with coupon coupon_id redeemed API_KEY returns redeemed id
Failure/Error: expect(json['coupon']).to eq(scooties_coupon.coupon)
expected: "6B2F5"
got: nil
(compared using ==)
Here is the code for the test I am running:
context 'with coupon coupon_id redeemed API_KEY' do
subject { post "/api/scooties_coupons/#{scooties_coupon.id}", headers: headers }
it "returns redeemed id" do
subject
expect(json['coupon']).to eq(scooties_coupon.coupon)
expect(json['redeemed']).to eq(scooties_coupon.redeemed)
end
end
my factory code:
FactoryBot.define do
factory :scooties_coupon do
coupon { "43MDA" }
redeemed { false }
email { "email#host.com" }
trait :redeemed do
redeemed { true }
end
end
end
here's the model:
class ScootiesCoupon < ApplicationRecord
has_paper_trail
before_create :set_coupon_code
after_update :send_activecamp_email_scootiescoupon
validates :email, presence: true, format: { with: /\A[^#\s]+#([^#.\s]+\.)+[^#.\s]+\z/ }
def update_redeemed(redeemed = true, email = nil)
email == self.email if email.present? || self.email.present?
self.created_at + self.expires_in.seconds < DateTime.now if self.expires_in.present?
self.update_attribute(:redeemed, redeemed)
end
private
def set_coupon_code
loop do
self.coupon = gen_coupon
break unless coupon_exists?
end
end
def coupon_exists?
self.class.exists?(coupon: coupon)
end
def gen_coupon
return SecureRandom.hex[0..4].upcase
end
end
I've tried to get rid of the validation on email but it doesn't seem to b e a major problem here, Could you give me a little idea of any other way to solve this problem?

Undefined method index? pundit testing in Rails

I am using Pundit for authorization in my application with rspec for testing.
require 'rails_helper'
describe SubjectPolicy do
subject { described_class.new(user, subject) }
let(:subject) { Subject.create }
context 'is an administrator' do
let(:role) { Role.create(role_name: 'admin') }
let(:user) { User.create(role_id: role.id) }
it { is_expected.to permit_actions([:index]) }
end
context 'is a teacher' do
let(:role) { Role.create(role_name: 'teacher') }
let(:user) { User.create(role_id: role.id) }
it { is_expected.to forbid_actions([:index]) }
end
end
When running the test for this spec test I receive the following error.
Failure/Error: it { is_expected.to permit_actions([:index]) }
NoMethodError: undefined method 'index?' for #<Subject:0x007fdcc1f70fd0>
There is a route for this index action and it is in my subjects_controller.
The code in the subject policy is very simple.
class SubjectPolicy < ApplicationPolicy
def index?
#user.is_admin?
end
end
Here is the index action in my subjects_controller
def index
#subjects = Subject.all
authorize #subjects
end
I am able to create subjects as an admin, and it does in fact block non-admins from accessing the index. But I am confused as to why this test would fail. I have this policy spec set up just like others and they are passing just fine. Any idea?

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.

2 Rspec tests fail due to nil:NillCLass error in model spec when others pass

I have the following Rspec test for a vote model, which includes a custom validation ensuring you can't vote on your own content, which is shown below. I am puzzled as to why only 2 of these tests fail with a nilclass error when the other tests within the spec pass.
#vote must be nil but why aren't the other tests failing with the same error?
vote.rb
validates :ensure_not_author
def ensure_not_author
votable = self.votable_type.downcase
errors.add(:user_id, "You can't vote on your own content.") if self.votable.user_id == self.user_id
end
factories
factory :answer do
user_id :user
question_id :question
body "you need to change your grip"
votes_count 0
correct false
end
factory :vote do
user_id :user
votable_id :answer
votable_type "Answer"
value 1
points 5
end
factory :user do |u|
u.sequence(:email) {|n| "test#{n}#hotmail.com"}
u.sequence(:username) {|n| "tester#{n}" }
u.password "password"
u.password_confirmation "password"
u.remember_me true
u.reputation 200
end
vote_spec.rb
require "spec_helper"
describe Vote do
before(:each) do
#user2 = FactoryGirl.create(:user)
#user = FactoryGirl.create(:user)
#answer = FactoryGirl.create(:answer, user_id: #user)
#vote = Vote.create(user_id: #user2.id, value: 1, points: 5, votable_id: #answer.id, votable_type: "Answer")
end
subject { #vote }
it { should respond_to(:user_id) }
it { should respond_to(:votable_id) }
it { should respond_to(:votable_type) }
it { should respond_to(:value) }
it { should respond_to(:points) }
describe 'value' do
before { #vote.value = nil }
it { should_not be_valid }
end
describe "user_id" do
before { #vote.user_id = nil }
it { should_not be_valid }
end
describe "votable_id" do
before { #vote.votable_id = nil }
it { should_not be_valid }
end
describe "votable type" do
before { #vote.votable_type = nil }
it { should_not be_valid }
end
describe "vote value" do
before { #vote.value = 5 }
it { should_not be_valid }
end
end
Failures:
1) Vote votable_id
Failure/Error: it { should_not be_valid }
NoMethodError:
undefined method `user_id' for nil:NilClass
# ./app/models/vote.rb:17:in `ensure_not_author'
# ./spec/models/vote_spec.rb:25:in `block (3 levels) in <top (required)>'
2) Vote votable type
Failure/Error: it { should_not be_valid }
NoMethodError:
undefined method `downcase' for nil:NilClass
# ./app/models/vote.rb:16:in `ensure_not_author'
# ./spec/models/vote_spec.rb:35:in `block (3 levels) in <top (required)>'
You validator ensure_not_author depends Vote#votable_type and Vote#votable to function well. And when you test validity of #vote, this validator will be tested.
However, in your "votable_id" testcase, you set votable_id to be nil. Later when you test #vote's validity with should_not be_valid, the ensure_not_author is called and failed at self.votable.user_id because ActiveRecord will query for Votable with votable_id.
Similarly, your "votable type" test case failed at self.votable_type.downcase since you set votable_type to be nil.
You should check the availability of the attributes in your validator before you send messages to them. Or write other validators to check them before ensure_not_author.

Resources