Ruby on rails 3 如何在factory_bot中定义数组/哈希?

Ruby on rails 3 如何在factory_bot中定义数组/哈希?,ruby-on-rails-3,rspec,factory-bot,Ruby On Rails 3,Rspec,Factory Bot,我试图编写一个测试,模拟Dropbox的REST服务返回的一些值,该服务使用嵌套哈希返回数组中的数据 由于返回的结果是一个内部带有has的数组,因此我很难理解如何对工厂进行编码。这里会有什么 Factory.define :dropbox_hash do ?? end Dropbox数据如下所示: ["/home", {"revision"=>48, "rev"=>"30054214dc", "thumb_exists"=>false, "bytes"=>0, "m

我试图编写一个测试,模拟Dropbox的REST服务返回的一些值,该服务使用嵌套哈希返回数组中的数据

由于返回的结果是一个内部带有has的数组,因此我很难理解如何对工厂进行编码。这里会有什么

Factory.define :dropbox_hash do
 ??
end
Dropbox数据如下所示:

 ["/home", {"revision"=>48, "rev"=>"30054214dc", "thumb_exists"=>false, "bytes"=>0, "modified"=>"Thu, 29 Dec 2011 01:53:26 +0000", "path"=>"/Home", "is_dir"=>true, "icon"=>"folder_app", "root"=>"app_folder", "size"=>"0 bytes"}] 
我希望在我的RSpec中有这样的工厂电话:

Factory.create(:dropbox_hash)

在最新版本的factory_girl中可以做到这一点,但这很尴尬,因为它是为构建对象而设计的,而不是数据结构。下面是一个例子:

FactoryGirl.define do
  factory :dropbox_hash, :class => 'Object' do
    ignore do
      url { "/home" }
      revision { 48 }
      rev { "30054214dc" }
      # more attributes
    end
    initialize_with { [url, { "revision" => revision, "rev" => rev, ... }] }
    to_create {}
  end
end
回顾一下这里奇怪的东西:

  • 每个工厂都需要一个有效的构建类,即使它没有被使用,所以我在这里传递了
    Object
    ,以防止它查找
    DropboxHash
  • 您需要使用
    ignore
    块忽略所有属性,这样它就不会在以后尝试将它们分配给数组,比如
    array.revision=48
  • 您可以使用
    initialize\u with
    告诉它如何将结果组合在一起。这里的缺点是您需要再次写出完整的属性列表
  • 您需要提供一个空的
    来创建
    块,这样它就不会试图调用
    array.save之后

我对做同样的事情很感兴趣,还想测试我的一个模型,它使用来自第三方API的内容哈希进行操作。我发现,通过使用factory_girl的一些内置功能,我能够干净地构建此类数据结构

下面是一个人为的例子:

  factory :chicken, class:Hash do
    name "Sebastian"
    colors ["white", "orange"]

    favorites {{
      "PETC" => "http://www.petc.org"
    }}

    initialize_with { attributes } 
  end
这里的主要技巧是,当您声明initialize_with时,factory_女孩将不再尝试将属性分配给结果对象。在这种情况下,它似乎也跳过了db存储。因此,我们不需要构建任何复杂的内容,只需将已经准备好的属性哈希作为内容传回。瞧

尽管没有实际使用该类,但似乎有必要为该类指定一些值。这是为了防止工厂试图基于工厂名称实例化类。我选择使用描述性类而不是对象,但这取决于您

当您使用以下哈希工厂之一时,仍然可以覆盖字段:

chick = FactoryGirl.build(:chicken, name:"Charles")
..但是,如果您有嵌套的内容,并且希望覆盖更深的字段,则需要增加初始化块的复杂性以进行某种深度合并

在您的例子中,您使用的是一些混合数组和散列数据,似乎应该在数据结构的各个部分之间重用Path属性。没问题-您知道内容的结构,因此可以轻松创建一个工厂来正确构造结果数组。我可以这样做:

  factory :dropbox_hash, class:Array do
    path "/home"
    revision 48
    rev "30054214dc"
    thumb_exists false
    bytes 0
    modified { 3.days.ago }
    is_dir true
    icon "folder_app"
    root "app_folder"
    size "0 bytes"

    initialize_with { [ attributes[:path], attributes ] }
  end

  FactoryGirl.build(:dropbox_hash, path:"/Chickens", is_dir:false)
您还可以随意省略不必要的值。让我们想象一下,只有Path和rev是真正必要的:

  factory :dropbox_hash, class:Array do
    path "/home"
    rev "30054214dc"
    initialize_with { [ attributes[:path], attributes ] }
  end

  FactoryGirl.build(:dropbox_hash, path:"/Chickens", revision:99, modified:Time.now)
我使用了OpenStruct:

factory :factory_hash, class:OpenStruct do
  foo "bar"
  si "flar"
end
编辑:抱歉,不能作为哈希值使用

我最后使用了一个静态版本,只是为了保持来自工厂系统的哈希

factory :factory_hash, class:Hash do
  initialize_with { {
    foo "bar"
    si "flar"
  } }
end

寻找更好的

当前RSpec版本(3.0)的后续版本:


只需像往常一样定义工厂,并使用它来接收散列而不是实例化的类。

让这项工作对我起作用,我可以根据需要将属性传递到散列中

factory :some_name, class:Hash do
  defaults = {
    foo: "bar",
    baz: "baff"
  }
  initialize_with{ defaults.merge(attributes) }
end

> build :some_name, foo: "foobar" #will give you
> { foo: "foobar", baz: "baff" }

你真的需要一个工厂吗?为什么不定义一个返回模拟响应的方法呢?我最后就是这么做的。但我认为工厂的目的是隔离这些东西。我仍然很好奇-看起来Hash和Array是类,如果我能得到正确的语法,这应该可以工作。我只在生成ActiveRecord模型实例时使用过它们。FactoryGirl打算更换固定装置。您可以看看RSpec的helper方法:这是一个很好的解决方案。它甚至可以与工厂的继承一起工作。这对我创建/构建来说是可行的,但是它失败了。要在
收藏夹中传递Rubocop linter
多行块,请使用
do..end
而不是
{..}
。如果要调用“创建”而不是
build
,可以将
跳过创建
添加到工厂。我发现我可以初始化任何自定义类,
initialize\u with
在类上下文中求值,也就是说,如果我有
initialize\u with{new(attributes)}
build:my\u工厂
将返回
MyClass.new(attributes)的结果
这还有一个额外的好处,即允许键为字符串。缺点是每次创建对象时都要修改单个对象,而不是新对象。看看散列的对象id,gem被重命名为FactoryBot。该文档的新位置是