RSpec 2 view specs: partial mocks

Are partial mocks supposed to work in rails-rspec 2.0.0.beta.12?

I’m trying to do things like

view.stub(:current_user).and_return(@user)
view.stub(:current_page?).and_return(false)

However, when I do that, the view uses its “ordinary” functionality, in
particular, assigns are no longer available as instance variables.

Also, as regards mocking of partials (the rails ones)

view.should_receive(:_render_partial).
with(hash_including(:partial => “widget/row”))

does not work as expected when render is called with objects like this

render @article
render @articles

In these cases the hash[:partial] does not contain the path to the
template, but the object(s).

I’m right now having little fun in beta hell. That’s just how it is and
by no means intend to distract you from your good work. I’m not yet
reasonably familiar with Rails 3 and RSpec 2 and so I’m stumbling around
alot in the sources and inch along in the debugger. One repeating point
of bother is that often stacktraces are barely informative when
delegation and dynamically generated code are in there. Unfortunately, I
can’t think of a way to improve on this.

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

On Jun 20, 2010, at 3:19 PM, Michael S. wrote:

Are partial mocks supposed to work in rails-rspec 2.0.0.beta.12?

I’m trying to do things like

view.stub(:current_user).and_return(@user)
view.stub(:current_page?).and_return(false)

However, when I do that, the view uses its “ordinary” functionality, in
particular, assigns are no longer available as instance variables.

Are you using rails beta 4? Before that release, the view() method
returned a different object every time.

In these cases the hash[:partial] does not contain the path to the
template, but the object(s).

view.should_receive(:anything_at_all) is specifying rails internals, and
has to be 100% precise. In this case, what you need to do is pretty
invasive, and I wouldn’t recommend it, but here it is:

require ‘spec_helper’

describe “widgets/index.html.erb” do
before(:each) do
@widgets = assign(:widgets, [
stub_model(Widget),
stub_model(Widget)
])
end

it “renders a list of widgets” do
view.stub(:_render_partial)
view.should_receive(:_render_partial).with(hash_including(:partial
=> @widgets[0]))
view.should_receive(:_render_partial).with(hash_including(:partial
=> @widgets[1]))

render

end
end

Can I hear a “blech!!!”.

Unfortunately, there is not a better way to do this yet, and there may
not be for some time. One of the big wins of rspec-rails-2 over
rspec-rails-1 is that it does not rely on monkey patches (except in
cases in which rails has changed since the last rails release, but
rspec-rails needs to work with the last release). This drastically
reduces the likelihood that rails releases will break rspec, which has
been a big pain-point in the past.

The downside of this is that we have to live with what Rails has to
offer, and view tests are not nearly as robust as controller tests in
rails. Yet :slight_smile:

Cheers,
David

On Sunday 20 June 2010, David C. wrote:

instance variables.

Are you using rails beta 4? Before that release, the view() method
returned a different object every time.

Yes, 3.0.0.beta4. TestCase#_view caches its return value.

  stub_model(Widget)

render

end
end

Can I hear a “blech!!!”.

Unfortunately, there is not a better way to do this yet, and there
may not be for some time.

It’s okay, if I hide away the yucky bits in a helper method.

spec/support/partial_helpers.rb

module PartialHelpers
def should_render(options)
options.assert_valid_keys(:partial, :count)
view.should_receive(:_render_partial).
with(hash_including(:partial => options[:partial]))
end

def should_not_render(options)
options.assert_valid_keys(:partial, :count)
view.should_not_receive(:_render_partial).
with(hash_including(:partial => options[:partial]))
end
end

in spec/spec_helpers.rb

config.include PartialHelpers, :example_group => {
:description => lambda { |description|
# FIXME this is a kludge as there is
# currently (rspec 2.0.0.beta.12)
# apparently no other way to identify a view
# example group.
description =~ /.html.erb$/
}
}

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

On Jun 20, 2010, at 5:18 PM, Michael S. wrote:

}
Here’s how rspec-rails-2 does it:
rspec-rails/lib/rspec/rails/example/view_example_group.rb at master · rspec/rspec-rails · GitHub

On Monday 21 June 2010, David C. wrote:

 # example group.
 description =~ /\.html\.erb$/

}

}

Here’s how rspec-rails-2 does it:
http://github.com/rspec/rspec-rails/blob/master/lib/rspec/rails/exam
ple/view_example_group.rb#L106

Thanks, that’s better.

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

On Jun 22, 2010, at 8:27 AM, Michael S. wrote:

variables.

view
end
end

#_view (and #view in turn) creates just one ActionView::Base instance
the first time it is called. That’s as it is intended. The effect is,
however, that only assigns up to that point are passed to that instance.
Consequently, later assigns are ignored.

Stubs and assigns are unrelated, so even though this might be a bug, I
don’t think it’s the cause of what you’re seeing.

On Sunday 20 June 2010, Michael S. wrote:

Are partial mocks supposed to work in rails-rspec 2.0.0.beta.12?

I’m trying to do things like

view.stub(:current_user).and_return(@user)
view.stub(:current_page?).and_return(false)

However, when I do that, the view uses its “ordinary” functionality,
in particular, assigns are no longer available as instance
variables.

I’ve found the reason:

rspec-
rails-2.0.0.beta.12/lib/rspec/rails/monkey/action_view/test_case.rb

def _view
@_view ||= begin
view = ActionView::Base.new(ActionController::Base.view_paths,
_assigns, @controller)

view
end
end

#_view (and #view in turn) creates just one ActionView::Base instance
the first time it is called. That’s as it is intended. The effect is,
however, that only assigns up to that point are passed to that instance.
Consequently, later assigns are ignored.

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

On Jun 22, 2010, at 9:58 AM, Michael S. wrote:

It is. If I put all the assign(:x, value) before I first access #view,
everything works as expected. The problem I had is entirely unrelated to
stubs.

Well, to be fair, the initial problem you cited in this thread (even in
the subject line) is “partial mocks”, not “assigns.”

That said, yes, this is a bug. I’ll see if we can do something about
that in Rails proper rather than rspec, which is just decorating the
rails behavior.

Cheers,
David

On Jun 22, 2010, at 10:06 AM, David C. wrote:

Stubs and assigns are unrelated, so even though this might be a bug,
I don’t think it’s the cause of what you’re seeing.

It is. If I put all the assign(:x, value) before I first access #view,
everything works as expected. The problem I had is entirely unrelated to
stubs.

Well, to be fair, the initial problem you cited in this thread (even in the subject line) is “partial mocks”, not “assigns.”

That said, yes, this is a bug. I’ll see if we can do something about that in Rails proper rather than rspec, which is just decorating the rails behavior.

https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4931

On Tuesday 22 June 2010, David C. wrote:

On Jun 22, 2010, at 8:27 AM, Michael S. wrote:

#_view (and #view in turn) creates just one ActionView::Base
instance the first time it is called. That’s as it is intended.
The effect is, however, that only assigns up to that point are
passed to that instance. Consequently, later assigns are ignored.

Stubs and assigns are unrelated, so even though this might be a bug,
I don’t think it’s the cause of what you’re seeing.

It is. If I put all the assign(:x, value) before I first access #view,
everything works as expected. The problem I had is entirely unrelated to
stubs. It’s just that when I do things in this order

view.stub(:something).and_return(‘foo’)
assign(:bar, 10)

The assign doesn’t have an effect. I might as well have

view.flash[:notice] = ‘Something is rotten.’
assign(:bar, 10)

and the effect would be the same. Assigns that come after the view is
first initialized are ignored.

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

On Tuesday 22 June 2010, David C. wrote:

On Jun 22, 2010, at 10:06 AM, David C. wrote:

On Jun 22, 2010, at 9:58 AM, Michael S. wrote:

Stubs and assigns are unrelated, so even though this might be a
bug, I don’t think it’s the cause of what you’re seeing.

It is. If I put all the assign(:x, value) before I first access
#view, everything works as expected. The problem I had is
entirely unrelated to stubs.

Well, to be fair, the initial problem you cited in this thread
(even in the subject line) is “partial mocks”, not “assigns.”

Yes, indeed. That’s because I was confused by the behavior I was seeing.
I found the root cause only because I had to dig in there for another
reason.

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/