Reputation: 24875
An action of a Rails controller makes an instance of a helper class (say SomeService
), which performs some work and returns a results, something along the lines of:
def create
...
result = SomeService.new.process
...
end
I want to stub what SomeService#process
returns.
My question is - how do I do this?
The following works:
allow_any_instance_of(SomeService).to receive(:process).and_return('what I want')
However, the rspec-mock
documentation discourages the use of allow_any_instance_of
for the reasons states here:
The rspec-mocks API is designed for individual object instances, but this feature operates on entire classes of objects. As a result there are some semantically confusing edge cases. For example in expect_any_instance_of(Widget).to receive(:name).twice it isn't clear whether each specific instance is expected to receive name twice, or if two receives total are expected. (It's the former.)
Using this feature is often a design smell. It may be that your test is trying to do too much or that the object under test is too complex.
It is the most complicated feature of rspec-mocks, and has historically received the most bug reports. (None of the core team actively use it, which doesn't help.)
I think the idea is to do something like this:
some_service = instance_double('SomeService')
allow(some_service).to receive(:process).and_return('what I want')
However, how do I make the controller use the double and not make a new instance
of SomeService
?
Upvotes: 4
Views: 4347
Reputation: 386
My suggestion is to remodel the way you interface with your Service Object:
class SomeService
def self.call(*args)
new(*args).tap(&:process)
end
def initialize(*args)
# do stuff here
end
def process
# do stuff here
end
def success?
# optional method, might make sense according to your use case
end
end
Since this is a project-wide convention, we know every .call
returns the service object instance, which we query for things such as #success?
, #error_messages
, etcettera (largely dependent on your use cases).
When testing clients of this class, we should only verify they call the class method .call
with the correct params, which is as straightforward as mocking the returned value.
The unit tests for this class method should attest it:
- calls .new
with the proper params;
- calls #process
on the created instance;
- returns the created instance (not the result of process).
Having a class method as main point of entry of your service object interface favors flexibility. Both #initialize
and #process
could be made private, but I prefer not to for testing purposes.
Upvotes: 0
Reputation: 230286
I usually do something like this.
let(:fake_service) { your double here or whatever }
before do
allow(SomeService).to receive(:new).and_return(fake_service)
# this might not be needed, depending on how you defined your `fake_service`
allow(fake_service).to receive(:process).and_return(fake_results)
end
Upvotes: 3