Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 332
Michael Trojanek Avatar asked Mar 30 '17 08:03

Michael Trojanek


1 Answers

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

like image 147
Fernando Favini Avatar answered Dec 04 '22 03:12

Fernando Favini