How to stub method with specific parameter (and leave calls with other parameters unstubbed) in Mocha?
Asked Answered
S

1

6

This question may seem like a duplicate of this one but the accepted answer does not help with my problem.

Context

Since Rails 5 no longer supports directly manipulating sessions in controller tests (which now inherit from ActionDispatch::IntegrationTest), I am going down the dark path of mocking and stubbing.

I know that this is bad practice and there are better ways to test a controller (and I do understand their move to integration tests) but I don't want to run a full integration test and call multiple actions in a single test just to set a specific session variable.

Scenario

Mocking/stubbing a session variable is actually quite easy with Mocha:

ActionDispatch::Request::Session.any_instance.stubs(:[]).with(:some_variable).returns("some value")

Problem is, Rails stores a lot of things inside the session (just do a session.inspect anywhere in one of your views) and stubbing the :[] method obviously prevents access to any of them (so session[:some_other_variable] in a test will no longer work).

The question

Is there a way to stub/mock the :[] method only when called with a specific parameter and leave all other calls unstubbed?

I would have hoped for something like

ActionDispatch::Request::Session.any_instance.stubs(:[]).with(:some_variable).returns("some value")
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(anything).returns(original_value)

but I could not find a way to get it done.

Systematism answered 30/3, 2017 at 8:35 Comment(2)
Try stub(:[]) instead of stubs(:[])Abisha
I contributed a few ideas to the original question, based on digging into the Mocha internals. Hope it helps somebody. Not posting my solution here cuz it's not that good, but better than nothing ;)Coherent
F
1

By what I see, this is a feature not available in mocha

https://github.com/freerange/mocha/issues/334

I know this does exist in rspec-mock

https://github.com/rspec/rspec-mocks/blob/97c972be57f2c060a4a7fb8a3c5700a5ede693f0/spec/rspec/mocks/stub_implementation_spec.rb#L29

One hacky way that you an do it though, is to store the original session in an object, then mock that whenever a controller receives session, it returns another mock object, and in this you may either return a mocked velue, or delegate the call to the original session

class MySession
  def initialize(original)
    @original = original
  end
  def [](key)
    if key == :mocked_key
      2
    else
      original[key]
    end
  end
end

let!(original_session) { controller.send(:session) }
let(:my_session) { MySession.new(original_session) }

before do
  controller.stubs(:session) { my_session }
end

Guess that mocha also allows you to do block mocking, so you don't need the class, but you need that original_session to be called

But I don't see a clean way

Fashionable answered 24/5, 2021 at 21:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.