【问题标题】:how to mock a database query response in rspec for testing other methods depending on the response如何在 rspec 中模拟数据库查询响应以根据响应测试其他方法
【发布时间】:2016-05-26 15:20:17
【问题描述】:

我的 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 从查询返回值。

感谢大家的所有贡献。

【问题讨论】:

  • 听起来你正试图同时测试这个方法和其他方法......当你可能只需要独立测试每个......但我不能确定,因为您没有包含您尝试编写的规范示例(即使它不工作),以及“其他方法”之一的代码和规范示例。即-我们很难调试您的代码的口头描述:) 请编辑您的问题并添加所有相关的代码部分:)
  • 谢谢@TarynEast 我已经从代码示例中添加了sn-ps。我正在尝试独立测试这些方法。如何模拟第一种方法返回的tasks,以便我可以将其用作第二种方法的输入(我实际上对测试感兴趣)
  • 我的第一印象是你的方法真的很长,可能需要拆分成更小的方法。试着让你的方法不超过 3-4 行。如果它更大 - 然后分成其他方法。例如,“tasks.each do”中的所有内容都应该是一种方法(将单个任务和“区域任务”作为参数传递,并返回“区域任务”的更新版本)。还将 ordering-by 部分取出到另一个以“district_tasks”为参数并传递有序集的方法中。
  • 那么你要测试的东西就更少了——你可以测试第一个(数据整理)方法是否需要 one 任务并做好准备。然后,您可以独立于数据整理方法测试排序方法。然后就可以测试整体方法是否为每个任务运行一次该方法,然后调用排序方法...
  • 好的,谢谢。 +100。效果很好。

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


【解决方案1】:

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

那么你要测试的就更少了——你可以只测试第一个(数据整理)方法需要一个任务并做好准备。然后,您可以独立于数据整理方法测试排序方法。然后就可以测试整体方法是否为每个任务运行一次该方法,然后调用排序方法...

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

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)
] }

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

使用双精度,您可以传递键/值的哈希值,这些键/值将表示双精度将响应的“方法”以及尝试该方法所产生的值。

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

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

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

【讨论】:

    猜你喜欢
    • 2019-04-07
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    • 2018-12-30
    相关资源
    最近更新 更多