Reputation: 5539
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.
Upvotes: 4
Views: 4329
Reputation: 571
I really like Nick's approach. I had the same problem myself and I was able to do the following. I believe mock_model would work as well.
post = stub_model(Post)
assigns[:post] = post
post.stub!(:comments).and_return([stub_model(Comment)])
Upvotes: 0
Reputation: 7944
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
Upvotes: 4
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.
Upvotes: 0
Reputation: 5539
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
Upvotes: 1