如何在 rspec 中模拟数据库查询响应以根据响应测试其他方法

how to mock a database query response in rspec for testing other methods depending on the response

我的 rails 应用程序中有一个方法正在执行一些复杂的查询和 returning 数据。

我还有其他方法,它们具有从第一个 returned 数据建模的特定方法。

我需要为其他方法编写 RSpec 测试...我需要传入第一个方法的结果(因为执行取决于此数据)

我如何模拟数据库查询的响应以用作测试我的其他方法的输入,而不必创建所有相关记录并调用第一个方法来 return 所需的数据?

第一种查询方式:

def agents_tasks_performed_this_week_or_assigned(kind = 1)

  condition = "..."
  values = {...}

  User.find(officer_id).supervised_users.active.joins(tasks: :ticket_subject)
    .where(condition, values)
    .select("users.id as agent_id, concat(users.first_name, ' ', users.last_name) as agent_name, date(tasks.completed_at) as completed_at, tasks.status, tasks.assigned_at")
end

测试方法:

def group_by_kind_and_date(supervisor_id = officer_id, tasks = []) # tasks is a result of the above method.
  district_tasks = {}

  tasks.each do |task|

    unless district_tasks[task.agent_id.to_s]
      district_tasks[task.agent_id] = new_rider_task_hash # this is a new hash of attributes
      district_tasks[task.agent_id][:id] = task.agent_id
    end

    if district_tasks[task.agent_id][:name].nil?
      district_tasks[task.agent_id][:name] = task.agent_name
    end

    ...

  district_tasks.values.sort_by{|item| item[:name].downcase }
end

RSpec 测试:

require 'spec_helper'

describe <Module_name> do

  let(:class_instance) { (Class.new {
    include <Module_name>
  }).new }

  describe "#group_by_kind_and_date" do

    it "should not include officers more than once in the response" do
      returned_ids = class_instance.group_by_kind_and_date(@supervisor.id, <...need to pass in tasks mock here...>).map{ |d| d[:id]}
      expect(returned_ids - returned_ids.uniq).to be_empty
    end
  end

end

所以...我需要得到类似模拟对象数组的东西,这些对象将具有所有 returned 属性。

例如:如果 returned 值为 tasks,模拟任务将具有以下属性:

task.agent_id, task.agent_name, task.completed_at, task.status and task.assigned_at

Note. A Task instance does not have all these above attributes... these attributes are available because of the .select returned values from the query.

感谢大家的贡献。

我的第一印象是您的方法真的很长,可能需要拆分成更小的方法。尽量让你的方法不超过 3-4 行。如果它更大 - 然后分成其他方法。例如,"tasks.each do" 中的所有内容都应该是一种方法(传入单个任务和 "district tasks" 作为参数,然后返回 "district_tasks" 的更新版本)。还将排序依据部分取出到另一种方法中,该方法将 "district_tasks" 作为 arg 并传递出有序集。

然后你就没有什么可测试的了——你可以只测试第一个(数据整理)方法接受一个任务并正确地准备它。然后您可以独立于数据整理方法测试排序方法。然后你可以测试整体方法是否为每个任务运行一次该方法,然后调用排序方法...

但是对于你的具体问题......你可以设置一个带双打的 let 例如

let(:tasks) { [
  double(:agent_id => '007', :agent_name => "Bond", :completed_at => 1.day.ago, :status => 'great', :assigned_at => 1.day.ago),
  double(:agent_id => '99', :agent_name => "99", :completed_at => 2.days.ago, :status => 'in the cold', :assigned_at => 1.day.ago)
] }

然后将其用作第二个参数。

对于 double,您可以传递 keys/values 的散列,它将代表 "methods" double 将响应的值以及尝试该方法所产生的值。

例如上面的任务示例将这样响应:

tasks[0].agent_id # => '007'

如果您需要添加不同的方法,同样只需将 keys/values 添加到您需要的散列中就可以了:)