Reputation: 3938
I'm new to testing, and can't figure out why this fails:
I have a test
require 'spec_helper'
require 'cancan/matchers'
describe User do
subject(:user) { User.create!(email: '[email protected]', password: '12345678', password_confirmation: '12345678', goal_id: '1', experience_level_id: '1', gender: 'Female') }
it "should be valid with a name, goal, password, password_confirmation, experience_level, and gender" do
user.should be_valid
end
I think this should pass. I can create a user through the site front end, but I'm getting the following fail message:
1) User should be valid with a name, goal, password, password_confirmation, experience_level, and gender
Failure/Error: subject(:user) { User.create!(email: '[email protected]', password: '12345678', password_confirmation: '12345678', goal_id: '1', experience_level_id: '1', gender: 'Female') }
RuntimeError:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
# ./app/models/user.rb:32:in `add_initial_program'
# ./spec/models/user_spec.rb:5:in `block (2 levels) in <top (required)>'
# ./spec/models/user_spec.rb:13:in `block (2 levels) in <top (required)>'
EDIT:
Adding User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable, :token_authenticatable
before_save :ensure_authentication_token
attr_accessible :email, :password, :password_confirmation, :remember_me, :goal_id, :experience_level_id, :gender, :role, :name, :avatar, :remote_avatar_url, :authentication_token, :measurement_units
validates :goal_id, presence: :true
validates :experience_level_id, presence: :true
validates :gender, presence: :true
before_create :add_initial_program, :assign_initial_settings
protected
def add_initial_program
prog_id = Program.for_user(self).first.id
self.user_programs.build( :cycle_order => 1, :group_order => 1, :workout_order => 1, :program_id => prog_id)
end
Line 32 is: prog_id = Program.for_user(self).first.id
and the method being referenced:
def self.for_user(user)
where(:goal_id => user.goal_id, :experience_id => user.experience_level_id, :gender => user.gender)
end
Upvotes: 0
Views: 262
Reputation: 10564
prog_id = Program.for_user(self).first.id
means, "Get the id of the first program which is associated with this user". Based on what we see, we can infer Program.for_user(self).first
is nil, and the id
method is being called on this nil. Are you sure that there is a Program model saved in your test DB which is associated with the user you create in your test? It doesn't appear that you are creating this model anywhere, and, based on your code, it is essential that there be an associated Program in place before you try to create your User.
EDIT:
You can initialize a Program in your subject block like so:
subject(:user) do
Program.create!(#whatever attributes are appropriate)
User.create!(email: '[email protected]', password: '12345678', password_confirmation: '12345678', goal_id: '1', experience_level_id: '1', gender: 'Female')
end
The last value in the block is the relevant return value for the subject block, so as long as the block returns a User model, things should be fine, even if you have more than one statement in the block. This, to me, is the cleanest initialization option that you can do changing your tests alone, because it's clear that User and Program are tightly bound together, such that to have a valid User you first must have an associated Program. I do think it would be even better to have logic in the User model that creates a Program if one doesn't exist (you might want to check out the query method #first_or_create), but multiple statements inside the block should work for now.
Upvotes: 1