views:

496

answers:

3

I've pastied the specs I've written for the posts/show.html.erb view in an application I'm writing as a means to learn RSpec. I am still learning about mocks and stubbing. This question is specific to the "should list all related comments" spec.

What I want is to test that the show view displays a post's comments. But what I'm not sure about is how to setup this test and then have the test iterate through with should contain('xyz') statements. Any hints? Other suggestions are also appreciated! Thanks.

---Edit

Some more information. I have a named_scope applied to comments in my view (I know, I did this a bit backwards in this case), so @post.comments.approved_is(true). The code pastied responds with the error "undefined method `approved_is' for #", which makes sense since I told it stub comments and return a comment. I'm still not sure, however, how to chain the stubs so that @post.comments.approved_is(true) will return an array of comments.

+1  A: 

I'm not sure if this is the best solution, but I managed to get the spec to pass by stubbing just the named_scope. I'd appreciate any feedback on this as well as suggestions for a better solution, given there is one.

  it "should list all related comments" do
@post.stub!(:approved_is).and_return([Factory(:comment, {:body => 'Comment #1', :post_id => @post.id}), 
                                      Factory(:comment, {:body => 'Comment #2', :post_id => @post.id})])
render "posts/show.html.erb"
response.should contain("Joe User says")
response.should contain("Comment #1")
response.should contain("Comment #2")

end

Eric M.
A: 

It reads a bit nasty.

You could do something like:

it "shows only approved comments" do
  comments << (1..3).map { Factory.create(:comment, :approved => true) }
  pending_comment = Factory.create(:comment, :approved => false)
  comments << pending_comments
  @post.approved.should_not include(pending_comment)
  @post.approved.length.should == 3
end

Or something to that effect. Spec the behavior, some method approved should return approved comments for a post. That could be a plain method or a named_scope. And pending would do something obvious as well.

You could also have a factory for :pending_comment, something like:

Factory.define :pending_comment, :parent => :comment do |p|
  p.approved = false
end

Or, if false is the default, you could do the same thing for :approved_comments.

A: 

Stubbing really is the way to go here.

In my opinion, all objects in controller and view specs should be stubbed with mock objects. There is no real need to spend time redundantly testing logic that should already be thoroughly tested in your model specs.

Here's an example how I would set up the specs in your Pastie…

describe "posts/show.html.erb" do

  before(:each) do
    assigns[:post] = mock_post
    assigns[:comment] = mock_comment
    mock_post.stub!(:comments).and_return([mock_comment])
  end

  it "should display the title of the requested post" do
    render "posts/show.html.erb"
    response.should contain("This is my title")
  end

  # ...

protected

  def mock_post
    @mock_post ||= mock_model(Post, {
      :title => "This is my title",
      :body => "This is my body",
      :comments => [mock_comment]
      # etc...
    })
  end

  def mock_comment
    @mock_comment ||= mock_model(Comment)
  end

  def mock_new_comment
    @mock_new_comment ||= mock_model(Comment, :null_object => true).as_new_record
  end

end
Nick Zadrozny