1

The Rails controller and views provide a view_context (usually an ActionView::Base object) that provides context for generating the views.

A common pattern is to wrap model instances in a Presenter class, in which case the view_context is usually also passed as an argument so the Presenter can call view methods (e.g. I8n.t(), Rails path helpers, etc...) as needed.

In my RSpec tests I use a mock to test the view_context behavior within the Presenter. For the path helpers specifically, I have to mock each path individually:

view_context = ActionView::Base.new
user = UserPresenter.new(FactoryBot.create(:user), view: view_context)

allow(view_context).to receive(:some_custom_path) do |opts|
  some_custom_path(opts)
end

Is there an easy way to programmatically mock all paths at once?

I suppose I could loop through the list of paths (not sure how to do that) and mock each one by one, but it feels like not the right approach.

Thanks!

EDIT: Actually the above snippet isn't even correct. It throws an error because the view_context (ActionView::Base) doesn't even implement :some_custom_path in the first place. I'm guessing it's a protection measure against stubbing something that doesn't exist.

user2490003
  • 10,706
  • 17
  • 79
  • 155

1 Answers1

2

Why do you want to mock all paths?

I'm assuming you are interested in actually mocking these calls and not just stub them. See the difference here.

Different presenters will probably call different path methods on their view_context. I recommend that you explicitly mock only the paths you are expecting to be called within the presenter you are testing.

You don't need to mock all paths because they are not all going to be called every time.

I would write your test as follows:

describe UserPresenter do
  subject(:user_presenter) { described_class.new(user, view: view_context)

  let(:user) { FactoryBot.create(:user) }
  let(:view_context) { instance_double(ActionView::Base) }
  let(:some_custom_path) { 'some/custom/path' }

  before do
    allow(view_context).to receive(:some_custom_path).and_return(some_custom_path)
  end

  it 'does something'
end

About the error you are seeing, yes, instance_double will protect you against stubbing a method that is not implemented on the receiver.

I do not recommend you do this, but if all you are looking for is a view object that will silently swallow calls to path methods then you can create a fake view like this:

class FakeView
  private

  def view_methods
    ActionView::Base.instance_methods - Object.instance_methods
  end

  def method_missing(meth, *params, &block)
    view_methods.include?(meth) ? nil : super
  end
end

and then use it in your tests like:

describe UserPresenter do
  subject(:user_presenter) { described_class.new(user, view: view_context)

  let(:user) { FactoryBot.create(:user) }
  let(:view_context) { FakeView.new }

  it 'does something'
end
Fito von Zastrow
  • 710
  • 5
  • 13