Arel
Arel

Reputation: 3938

RSpec test fails when checking if user.should be_valid

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

Answers (1)

Matt
Matt

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

Related Questions