0

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
5
  • app/models/user.rb:32 is the culprit Commented Aug 9, 2013 at 19:08
  • Or at least that's where the culprit is manifesting itself. Please share your model if you want more help. Commented Aug 9, 2013 at 19:09
  • Ok, I added the relevant part of the User model. I can create a user through the website, so I don't understand why the spec would fail because of a problem with the User model. Commented Aug 9, 2013 at 19:31
  • Could it be that a program doesn't exist in the test database, so that is why it's nil? Commented Aug 9, 2013 at 19:32
  • 1
    This doesn't answer your question, but I would recommend against hard-coding IDs in your tests. I'd use something like FactoryGirl instead. Commented Aug 9, 2013 at 19:33

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

You're right! I don't have a program model yet in the test. How would I create that, just by adding another describe block for Program?
I would include it in your subject method - the value returned from the subject method is what is important, but you can do more in the block. For instance, you can do `subject{Program.create!(...); User.create!(...)} and, as long as your Program is appropriately linked to your User, this should be fine. I'll edit the answer to show this more clearly than in the comment.
Awesome! Thanks. I've been trying to figure out how to do that.
Also, thanks for showing me how to add multiple things to the subject. That's a huge help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.