Problem with view spec - works inside the browser but spec fails with nil object

Hi,

I’ve been trying to find an answer for this problem in the last couple
hours, but I think no discussion was about this exact same thing. So
here it
goes, hope someone can help.

I’m trying to spec a view which works correctly on the browser, but that
generates the following error when I run “rake spec:views”.

ActionView::TemplateError in ‘/survey/show should display the “question
4”
heading correctly’
You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.position
On line #1 of app/views/survey/_question_for_candidate.rhtml

1: <div class="question question-<%= question.position %>">
2:   <p class="heading">
3:     <span class="number"><%= question.position %>.</span>
4:     <span class="description"><%= question.description %></span>

RSpec is telling me that the “question” object is nil. I can’t figure it
out
why.

Here’s the spec that generates the error (the “it” block should test
some
tags inside the “response” object):

require File.dirname(FILE) + ‘/…/…/spec_helper’

describe ‘/survey/show’ do
fixtures ‘questions’, ‘alternatives’

before(:each) do
assigns[:configurations] = {:survey_name => ‘Whatever’}
assigns[:questions] = Array.new
assigns[:questions][4] = questions(:faixa_etaria)
@faixa_etaria = questions(:faixa_etaria)
end

it ‘should display the “question 4” heading correctly’ do
render ‘survey/show’
end
end

Here’s the :faixa_etaria Question fixture I’m using:

faixa_etaria:
id: 1
question_type_id: 1
description: ‘Em qual das faixas etárias abaixo você se inclui?’
position: 4

(The description value is in Portuguese).

Here’s the “show” method inside the SurveyController:

def show
@configurations = {}
Configuration.find(:all).each { |c| @configurations[c.name.to_sym] =
c.value }
@questions = {}
Question.find(:all).each { |q| @questions[q.position] = q }
end

Here’s the piece of code inside the “show.rhtml” template that’s calling
the
helper method that will cause that error:

<%= render_question(@questions[4]) %>

Here’s the “render_question” method implementation, inside the
SurveyHelper
class (invoking “render” seems to be the problem, but I don’t know why):

def render_question(question)
render(:partial => ‘question_for_candidate’, :locals => {:question
=>
question})
end

The :locals Hash has :question as a key, and that is the variable that
RSpec
is complaining about inside the partial.

Finally, here’s the “question_for_candidate” partial:

<%= question.position %>. <%= question.description %>

<% for alternative in question.alternatives -%> <%= radio_button 'candidate', "question_#{question.id}", alternative.id%> <%= alternative.description %>
<% end -%>

I’m not that experienced with Rails nor RSpec, so I’m totally lost about
this problem.

You are trying to test a lot of things at the same time, which is one of
the reasons that it is now hard to diagnose a problem.

I would tackle this by
a) writing a separate spec for the partial _question_for_candidate
b) write a separate spec for the helper that renders the question
b) in the view spec for /survey/show use should_receive to determine
that the helper is actually being called
c) not use any fixtures in the view specs but instead use mocks for
that: it’s faster and you can check exactly what needs to happen instead
of relying on what happens to be in the fixture.

I’m afraid that I don’t see immediately why your current setup won’t
work, but untangling things along the lines sketched above should
hopefully get you to a situation where it becomes easier to see what
happens.

before(:each) do
assigns[:configurations] = {:survey_name => ‘Whatever’}
assigns[:questions] = Array.new
assigns[:questions][4] = questions(:faixa_etaria)

I am a little bit suspicious about this construction, though. I’m not
sure whether assigns is a normal Array in this case, so I’d create the
Array and populate it before handing it to assigns:

@questions = Array.new
@questions[4] = …
assigns[:questions] = @questions

Kind regards,

Hans

Hi Hans,

I rewrote my specs the way you suggested and now everything works!

For those who are interested, here’s how the code looks like now (after
following Hans’ tips).

a) writing a separate spec for the partial _question_for_candidate

require File.dirname(FILE) + ‘/…/…/spec_helper’

module QuestionForCandidatePartialHelper
def get_question_type_mock(type)
mock_model(QuestionType, {:name => type})
end

def render_question_for_candidate_partial
render(:partial => ‘survey/question_for_candidate’, :locals =>
{:question => @question})
end
end

describe ‘partial survey/_question_for_candidate.rhtml’ do
include QuestionForCandidatePartialHelper

before(:each) do
@question = mock_model(Question, {
:id => 1,
:description => ‘The description’,
:position => 4,
:alternatives => [mock_model(Alternative, {:id => 1, :description
=>
‘Alternative 1 Description’})],
})
@question_type_exclusive = get_question_type_mock(‘exclusive’)
@question_type_multiple = get_question_type_mock(‘multiple’)
end

it ‘should render the top of the template no matter the question type’
do
@question.stub!(:question_type).and_return(@question_type_exclusive)
render_question_for_candidate_partial
response.should have_tag(‘div.question.question-4’) do
with_tag(’.heading .number’, ‘4.’)
with_tag(’.heading .description’, @question.description)
end
end

it ‘should render an exclusive-answer question’ do
@question.stub!(:question_type).and_return(@question_type_exclusive)
render_question_for_candidate_partial
response.should have_tag(‘div.question.question-4’) do
with_tag(‘input[type=?]’, ‘radio’)
without_tag(‘input[type=?]’, ‘checkbox’)
end
end

it ‘should render a multiple-answer question’ do
@question.stub!(:question_type).and_return(@question_type_multiple)
render_question_for_candidate_partial
response.should have_tag(‘div.question.question-4’) do
with_tag(‘input[type=?]’, ‘checkbox’)
without_tag(‘input[type=?]’, ‘radio’)
end
end
end

b) write a separate spec for the helper that renders the question

require File.dirname(FILE) + ‘/…/spec_helper’

describe SurveyHelper do
before(:each) do
@question = mock_model(Question)
end

it ‘should render a question using the question_for_candidate partial’
do
self.should_receive(:render).with(:partial =>
‘question_for_candidate’,
:locals => {:question => @question})
render_question(@question)
end
end

b) in the view spec for /survey/show use should_receive to determine
that the helper is actually being called

require File.dirname(FILE) + ‘/…/…/spec_helper’

describe ‘/survey/show’ do
before(:each) do
assigns[:configurations] = {:survey_name => ‘Whatever’}
@positions = [4, (9…22).to_a].flatten
@questions = Array.new
@positions.each do |position|
@questions[position] = mock_model(Question, {
:id => 1,
:description => ‘The description’,
:position => 4,
:alternatives => [mock_model(Alternative, {:id => 1,
:description =>
‘Description’})],
:question_type => mock_model(QuestionType, {:name =>
‘exclusive’})
})
end
assigns[:questions] = @questions
end

it “should render the ‘survey/question_for_candidate’ partial” do
args = {:partial => ‘question_for_candidate’, :locals => {:question
=>
an_instance_of(Question)}}
template.should_receive(:render).with(args).exactly(@questions.nitems
).times
render ‘survey/show’
end
end

c) not use any fixtures in the view specs but instead use mocks for
that: it’s faster and you can check exactly what needs to happen instead
of relying on what happens to be in the fixture.

As you can see mocks are now all over the place. No fixtures were used
:wink:

Thanks a lot,

Caio