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

时间:2016-02-15 16:14:02

标签: ruby-on-rails ruby postgresql rspec mocking

我的rails应用程序中有一个方法,它执行一些复杂的查询并返回数据。

我有其他方法可以使用特定的方法对第一个返回的数据进行建模。

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

如何模拟数据库查询的响应,以便在测试我的其他方法时用作输入,而无需创建所有相关记录并调用第一种返回所需数据的方法?

首先查询方法:

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

所以......我需要获得一些类似于具有所有返回属性的模拟对象。

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

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

请注意。 Task实例没有以上所有属性......由于查询中返回了.select个值,因此这些属性可用。

感谢所有人的贡献。

1 个答案:

答案 0 :(得分:2)

我的第一印象是你的方法很长,可能需要分成更小的方法。尝试使您的方法长度不超过3-4行。如果它更大 - 然后分成其他方法。例如,“tasks.each do”中的所有内容应该是一种方法(将单个任务和“区域任务”作为参数传递并返回“district_tasks”的更新版本)。同时将order-by部分取出另一个方法,将“district_tasks”作为arg并传递出有序集。

然后你没有更少的测试 - 你可以测试第一个(数据争用)方法接受一个任务并准备它。然后,您可以独立于数据争用方法测试排序方法。然后你可以测试整个方法为每个任务运行一次该方法,然后调用排序方法......

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

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,您可以传递一个键/值的哈希值,它将表示双重响应的“方法”以及尝试该方法时产生的值。

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

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

如果你需要添加不同的方法,同样只需将键/值添加到你需要的哈希中,你就可以了:)