Rails 测试从控制器调用的方法

Rails test that method is called from the controller

我有一个调用服务的控制器函数,我想测试是否使用正确的参数调用该服务。

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

如何编写测试以确保 EsignGenieSendByTemplate.new(client: client, external_documents: external_documents, form_values: params[:form_values]) 被正确调用?

你需要的是一个叫做 expecting messages 的东西。

我一般是这样写的:

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

我将从向服务添加工厂方法开始:

class EsignGenieSendByTemplate
  # ...
  def self.process(**kwargs)
    new(**kwargs).process
  end
end

这种代码几乎是任何类型服务对象的样板,并在服务对象和它的消费者(如控制器)之间提供更好的API。

此方法应包含在您的服务规范中的示例中。

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

你的控制器应该只调用工厂方法而不是实例化 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

控制器和服务之间更好的 API 让您可以在 EsignGenieSendByTemplate class 上设置期望,这样您就不必在 expect_any_instance 上胡闹了或存根 .new 方法。

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