Reputation: 6674
I have a controller function that calls a service and I want to test that the service is called with the right arguments.
def send_for_signature
client = Client.find(params[:client_id])
external_documents = params[:document_ids].map{|id| ExternalDocument.find(id)}
service = EsignGenieSendByTemplate.new(client: client, external_documents: external_documents, form_values: params[:form_values])
result = service.process
if result["result"] == "success"
head 200
else
render json: result["error_description"], status: :unprocessable_entity
end
end
How would I write my test to ensure that EsignGenieSendByTemplate.new(client: client, external_documents: external_documents, form_values: params[:form_values])
is called correctly?
Upvotes: 0
Views: 1050
Reputation: 101811
I would start by adding a factory method to the service:
class EsignGenieSendByTemplate
# ...
def self.process(**kwargs)
new(**kwargs).process
end
end
This kind of code is boilerplate in almost any kind of service object and provides a better API between the service object and its consumers (like the controller).
This method should be covered by an example in your service spec.
describe '.process' do
let(:options) do
{ client: 'A', external_documents: 'B', form_values: 'C' }
end
it "forwards its arguments" do
expect(described_class).to recieve(:new).with(**options)
EsignGenieSendByTemplate.process(**options)
end
it "calls process on the instance" do
dbl = instance_double('EsignGenieSendByTemplate')
allow(described_class).to recieve(:new).and_return(dbl)
expect(dbl).to recieve(:process)
EsignGenieSendByTemplate.process(**options)
end
end
Your controller should just call the factory method instead of instanciating EsignGenieSendByTemplate:
def send_for_signature
client = Client.find(params[:client_id])
# Just pass an array to .find instead of looping - this create a single
# db query instead of n+1
external_documents = ExternalDocument.find(params[:document_ids])
result = EsignGenieSendByTemplate.process(
client: client,
external_documents: external_documents,
form_values: params[:form_values]
)
if result["result"] == "success"
head 200
else
render json: result["error_description"], status: :unprocessable_entity
end
end
This better API between the controller and service lets you set an expectation on the EsignGenieSendByTemplate
class instead so you don't have to monkey around with expect_any_instance
or stubbing the .new
method.
it 'requests the signature' do
expect(EsignGenieSendByTemplate).to receive(:process).with(client: 'A', external_documents: 'B', form_values: 'C')
get :send_for_signature, params: { ... }
end
Upvotes: 1
Reputation: 3251
What you need is something called expecting messages.
I usually write something like this:
it 'requests the signature' do
expect(EsignGenieSendByTemplate).to receive(:new).with(client: 'A', external_documents: 'B', form_values: 'C')
get :send_for_signature, params: { ... }
expect(response.status).to have_http_status(:success)
end
Upvotes: 1