I am trying to test a nested route however I am getting a undefined method customer for nil:NilClass. I have got the following RSpec test:
let(:valid_attributes) do
{alert_type: 'Error', subject: 'Triple-buffered responsive system engine',
state: 'Allocated', run_date: '2013-09-30 13:56:58', priority: 1, examined_on: '2013-09-30 13:56:58'
}
end
let(:valid_card_attributes) do
{name_on_card: 'Botsford', expiration_date: '2013-09-24',
expiration_month: '2013-09-24', valid_year: '2013-09-24', valid_month: '2013-09-24',
card_number: '2456-6996-2785-3769', bin: '8384-0294'
}
end
let(:valid_violation_attributes) do
{internal_code: 'Subsche', rule_priority: '96',
rule_id: '10', account_id: '10',
authorisation_id: '356'
}
end
let(:valid_customer_attr) do
{ first_name: 'CustomerString', last_name: 'CustomerString',
address1: 'CustomerString' , address2: 'CustomerString', post_code: 'CustomerString',
telephone: 'CustomerString', country: 'CustomerString', member_id: 1,
merchant_id: 1
}
end
let(:valid_session) { {} }
context 'JSON' do
describe 'GET show' do
it "delivers an alert with ID in JSON when a user requests '/api/alerts/id'" do
alert = Alert.create! valid_attributes
get :show, {:id => alert.to_param}, :format => :json
assigns(:alert).should eq(alert)
end
end
describe 'GET customer'
it 'delivers an alert with a customer and associated card' do
alert = Alert.create! valid_attributes
customer = Customer.create! valid_customer_attr
card = Card.create! valid_card_attributes.merge(customer_id: customer.id)
Violation.create! valid_violation_attributes.merge(alert_id: alert.id, customer_id: customer.id)
get :customer, {:id => alert.to_param}, :format => :json
assigns(:alert).customer.cards.first.should eq(card)
end
end
end
The console.log of the error that I am getting back is:
NoMethodError: undefined method `customer' for nil:NilClass
./app/models/alert.rb:10:in `customer'
./app/controllers/alerts_controller.rb:22:in `customer'
./spec/controllers/alerts_controller_spec.rb:33:in `block (3 levels) in <top (required)>'
Doing violation.first.customer just simply returns the first violation and the associated customer.
Be interested if anyone can shed any light on this.
violations.first in your Alert class is nil.
You're not setting up any Violations for your Alert in your test, so #alert.violations will be an empty array. Calling first on an empty array is nil, and you can't call customer on nil.
You can get around this using try (eg. violations.first.try(:customer)), or more correctly to check for the presence of any violations (eg. violations.first.customer if violations.any?).
Related
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
My RSpec for ResidenceInformations Controller is resulting in failure when I run 'rake spec'. My Controller code is as follows
class ResidenceInformationsController < ApplicationController
def index
end
def show
render partial: 'subregion_select'
end
def new
#residence_info = ResidenceInformation.new
#saved_residence_info = ResidenceInformation.where('applicant_id=?', current_applicant.id)
end
def create
#residence_info = ResidenceInformation.new(residence_informations_params)
#residence_info.state = params[:order][:state]
#residence_info.applicant_id = current_applicant.id
##residence_info.residence_type_id = params[:residence_type][:residence_type_id]
#residence_info.save!
if cookies[:residence_next] == 'true' && cookies[:add_another] == 'false'
redirect_to new_employment_information_path
elsif cookies[:residence_next] == 'false' && cookies[:add_another] == 'true'
#cookies[:add_another] = 'false'
redirect_to request.referer
elsif cookies[:residence_next] == 'false' && cookies[:add_another] == 'false'
redirect_to request.referer
end
end
def edit
#residence_info = ResidenceInformation.find(params[:id])
end
def update
end
def destroy
end
def subregion_options
render partial: 'subregion_select'
end
private
def residence_informations_params
params.require(:residence_information).permit(:address1, :address2, :country, :state, :city, :zip, :reason_for_moving, :resident_since, :resident_upto, :was_notice_given, :monthly_rent, :residence_type_id, :applicant_id)
end
end
And This is my spec file for the above mentioned controller
require 'rails_helper'
RSpec.describe ResidenceInformationsController, :type => :controller do
describe 'GET #new' do
login_applicant
it 'should have a current_applicant' do
subject.current_applicant.should_not be_nil
end
it 'assigns a new ResidenceInformation to #residenceinfo' do
#residenceinfo = FactoryGirl.create(:residence_information, address1: 'dsada', country: 'india', state: 'wb', city: 'kolkata', zip: '700091', reason_for_moving: 'none', resident_since: '2015-01-05', was_notice_given: 'true', residence_type_id: 1, applicant_id: 13)
#puts"****************#{residenceinfo.inspect}***********************"
get :new
assigns(:residenceinfo).should be_a_new(ResidenceInformation)
end
it 'renders the :new template' do
get :new
response.should render_template :new
end
end
describe 'POST create' do
login_applicant
context 'with valid attributes' do
it 'should have a current_applicant' do
subject.current_applicant.should_not be_nil
end
it 'create a new ResidenceInformation' do
expect{
post :create, residenceinformation: FactoryGirl.create(:residence_information, address1: 'dsada', country: 'india', state: 'wb', city: 'kolkata', zip: '700091', reason_for_moving: 'none', resident_since: '2015-01-05', was_notice_given: 'true', residence_type_id: 1, applicant_id: 13)
}.to change(ResidenceInformation, :count).by(1)
end
end
end
end
I am getting the following error when running this spec file
Failure/Error: assigns(:residenceinfo).should be_a_new(ResidenceInformation)
expected nil to be a new ResidenceInformation(id: integer, address1: string, address2: string, country: string, state: string, city: string, zip: string, monthly_rent: integer, reason_for_moving: string, resident_since: date, resident_upto: date, was_notice_given: boolean, created_at: datetime, updated_at: datetime, residence_type_id: integer, applicant_id: integer)
# ./spec/controllers/residence_informations_controller_spec.rb:17:in `block (3 levels) in <top (required)>'
As I am completely new to RSpec , so I would appreciate any kind of help I get from you people. Thanks in advance
EDIT
Sorry , I had given the wrong spec file. now given the right one
Your instance variable is named #residence_info and you are checking :residenceinfo. Try it with the underscore:
assigns(:residence_info).should be_a_new(ResidenceInformation)
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.
My rspec tests are failing when I try to use .reload to test the Update method.
The error
Admin::CompaniesController PUT update valid attributes changes #company's attributes
Failure/Error: #company.reload
TypeError:
no implicit conversion of nil into Hash
# ./spec/controllers/admin/admin_companies_controller_spec.rb:81:in `block (4 levels) in <top (required)>'
The spec
# spec/controllers/admin_companies_controller_spec.rb
describe 'PUT update' do
before :each do
#company = create(:company, name: "Rockstars", phone: "3035551212")
end
context "valid attributes" do
it "changes #company's attributes" do
put :update, id: #company, company: attributes_for(:company, name: "Fishstars")
#company.reload #line 81
expect( #company.name ).to eq("Fishstars")
end
end
end
I have also tried (1) using let!(), (same error)
describe 'PUT update' do
let!(:company) { create(:company, name: "Rockstars", phone: "303555121d") }
context "valid attributes" do
it "changes company's attributes" do
put :update, id: company, company: attributes_for(:company, name: "Fishstars")
company.reload #line 81
expect( company.name ).to eq("Fishstars")
end
end
end
I have also tried (2) (same error)
before :each do
#company = create(:company, name: "Rockstars", phone: "3035551212")
end
it "changes #company's attributes" do
#attr = { name: "Fishstars" }
put :update, id: #company.id, :company => #attr
#company.reload
expect( #company.name ).to eq("Fishstars")
end
My first thought was that the variable #company was nil, but if I comment out line 81 (indicated) the test fails as expected.
Update
inserting 'byebug' just before line 81 allows me to see what #company looks like:
(byebug) #company
#<Company id: 1, name: "Rockstars", hq_address: "MyString", hq_city: "MyString", hq_state: "MyString", country: "MyString", description: "Description 1", phone: "3035551212", email: "Example2email#gmail.com", owner_id: 2, contact_name: "MyString", created_at: "2014-07-11 19:02:24", updated_at: "2014-07-11 19:02:24", active: true, logo_file_name: nil, logo_content_type: nil, logo_file_size: nil, logo_updated_at: nil, slug: "rockstars", refund_policy: "Refund policy">
(byebug) #company.reload
TypeError Exception: no implicit conversion of nil into Hash
nil
I wasn't able to figure out why .reload wasn't working, but I replaced it with a find_by_id call and my tests are working properly now:
# new code - effectively tests the update method
#company = Company.find_by_id(#company)
# original code - causes errors
#company.reload #line 81
OK, here's my rspec code ...
before(:each) do
#attr = { :greeting => "Lorem ipsum", :recipient => #recipient }
end
it "should redirect to the home page" do
puts "spec: #attr = #{#attr}"
puts "spec: recipient = #{#attr[:recipient]}"
post :create, :card => #attr
response.should redirect_to(root_path)
end
Now the output from this is:
spec: #attr = {:greeting=>"Lorem ipsum", :recipient=>#<User id: 2, first_name: "Example", last_name: "User", email: "recipient#example.com", created_at: "2011-12-22 04:01:02", updated_at: "2011-12-22 04:01:02", encrypted_password: "2d1323ad5eb21fb5ae5e87dfa78a63b521c56833189cc049ee2...", salt: "2679fcc29a30e939541cb98cb65d1d508035fea0eff1136037a...", admin: false>}
spec: recipient = #<User:0xac5d80c>
So we can see that recipient is a User.
On the controller side, we see have ...
def create
puts "create: Params = #{params}"
#card = current_user.sent_cards.build(params[:card])
if #card.save
flash[:success] = "Card created!"
redirect_to root_path
else
render 'pages/home'
end
end
With a display of ...
create: Params = {"card"=>{"greeting"=>"Lorem ipsum", "recipient"=>"2"}, "controller"=>"cards", "action"=>"create"}
and I see an error of ...
1) CardsController POST 'create' success should create a card
Failure/Error: post :create, :card => #attr
ActiveRecord::AssociationTypeMismatch:
User(#90303150) expected, got String(#76171330)
# ./app/controllers/cards_controller.rb:7:in `create'
# ./spec/controllers/cards_controller_spec.rb:47:in `block (5 levels) in <top (required)>'
# ./spec/controllers/cards_controller_spec.rb:44:in `block (4 levels) in <top (required)>'
So ... how did the User object get changed into its id as a string? Any ideas?
You cannot pass an entire object as a parameter. Rails replaces the object with its id if it has one or else passes a string representation of the object i.e. #<User:0xac5d80c> for your case if it doesn't find the id.
So for your case, you should rename the :recipient parameter to :recipient_id. Then
#card = current_user.sent_cards.build(params[:card])
will create your card with the associated recipient as we have passed in the recipient_id.