Occasionally, I find myself in a situation where I want to have a mock
obj returned if a method is called with a particular argument but
handled normally otherwise. For example, lets say I have a Model
named User and I am specing a controller that sends messages from one
user to another. When User.find is called for the user who is making
the request I want it to run normally but when User.find is called for
the receiver I want it to return a mocked obj. In this case, I can do
something like (gist:352305 · GitHub):
user = mock_model(User)
User.stub!(:find).at_least(1).and_return do |id|
if id == mock_user.id.to_s
user
else
User.find_by_id(id)
end
end
If I didn’t have another method that allowed me to find a User by it’s
id this won’t work.
User.stub!(:find).at_least(1).and_return do |id|
Is there an easier way to accomplish this?
Not really. When you stub a method, the framework overrides that method
with its own implementation. There’s no mechanism in place to say “pass
the message onto the real object if it doesn’t have the arguments I’m
interested in.” I’m not sure of any framework that does that. Maybe RR,
but I’m not sure.
the receiver I want it to return a mocked obj. In this case, I can
end
arguments I’m interested in." I’m not sure of any framework that
does that. Maybe RR, but I’m not sure.
Good luck.
David
I can’t help but chime in here that I would be pretty irritated to
come across a test that mixed up using mocks and real objects,
especially when they’re the same class. Can you not use a mock for the
other instance of User too?
Matt, I totally hear you. In this contrived example, you probably
could but in the project I am working on it would be very difficult.
One of the challenges of joining a project already in progress…
For anyone who might come across this message looking for a solution
to the same problem, I wrote the following function to take care of it
(gist:352449 · GitHub)
def stub_find_for_specific_values(model, stubs)
model.stub!(:find).at_least(1).and_return do |id|
if stubs.has_key? id
stubs[id]
else
model.find_by_id(id)
end
end
end
#example below will return the mock_user if its id is search for
otherwise find will search as normal
mock_user = mock_model(User)
stub_find_for_specific_values(User, mock_user.id => mock_user)
Matt, I totally hear you. In this contrived example, you probably
could but in the project I am working on it would be very difficult.
One of the challenges of joining a project already in progress…
I used to do the following when I used rspec mocks.
user = mock_model(User)
find_method = User.method(:find)
User.stub!(:find).at_least(1).and_return do |id|
if id == mock_user.id.to_s
user
else
find_method.call(id) # May need to instance_eval here, but I
think .call is sufficient.
end
end
In fact this sort of inspired me to mock.proxy method call. Of course
YMMV
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.