如何在factory_bot中定义数组/哈希?

82

我正在尝试编写一个测试,模拟从Dropbox的REST服务返回数据的一些返回值,该数据以数组形式返回,其中包含一个嵌套的哈希。

由于返回结果是一个包含哈希的数组,我很难弄清楚如何编写我的工厂代码。这里应该怎么做?

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)

你真的需要一个工厂来做这个吗?为什么不只是定义一个返回模拟响应的方法呢? - zetetic
这就是我最终做的事情。但是我认为工厂的重点是隔离这些东西。 我仍然很好奇 - 哈希和数组似乎都是类,如果我能找到正确的语法,这应该可以工作。 - Doug
我只用过它们来生成ActiveRecord模型实例。FactoryGirl旨在取代fixtures。你可以看一下RSpec的helper方法:https://www.relishapp.com/rspec/rspec-core/v/2-9/docs/helper-methods - zetetic
5个回答

166

我对做同样的事情很感兴趣,也是为了测试我使用第三方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_girl将不再尝试将属性分配给结果对象。它似乎也跳过了数据库存储。因此,我们不需要构建任何复杂的内容,只需将已准备好的属性哈希作为我们的内容返回即可。大功告成。

似乎需要指定一些类的值,尽管实际上没有使用该类。这是为了防止factory_girl根据工厂名称尝试实例化类。我选择使用描述性类而不是Object,但这由你决定。

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

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)

您仍然可以省略不必要的值。假设只有路径和版本是真正必需的:

  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)

这是一个很棒的解决方案。它甚至可以与工厂继承一起使用。 - David Pelaez
谢谢您。为了通过Rubocop linter中的“favorites”多行块,请使用do..end而不是{{..}} - scarver2
1
如果你想调用'create'而不是'build',可以在工厂中添加'skip_create'。 - Sairam
1
太好了。我发现我可以初始化任何自定义类,initialize_with 在类上下文中进行评估,即如果我有 initialize_with { new(attributes) },那么 build :my_factory 将返回 MyClass.new(attributes) 的结果。 - mikdiet
谢谢。这对于存根数据库查询返回的哈希数组非常有用。 - Jason Miller
显示剩余2条评论

11

我成功地让它运行了,而且我可以根据需要将属性传递到哈希表中。

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" }

3
这样做的另一个好处是可以让键(key)成为字符串。 - crftr
这里的缺点是每次创建对象时都修改了同一个对象,而不是新建一个对象。请查看哈希表的 object_id。 - Anthony

8

针对当前RSpec版本(3.0)的后续操作:

按照惯例定义你的工厂,然后使用FactoryBot.attributes_for来获取哈希而不是实例化的类。


2
这个 gem 已经被重新命名为 FactoryBot。相关的文档现在位于 https://www.rubydoc.info/github/thoughtbot/factory_bot/FactoryBot/Syntax/Methods#attributes_for-instance_method。 - scarver2
只有在您有一个类可以链接到工厂定义时,这才能正常工作(例如:factory :my_class do ... end 将尝试实例化 MyClass 类)。为了克服这个问题,您可以通过传递 class 参数来解决。factory :my_class, class Hash do ... end - Thiago Petrone

6

你可以在最新版的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_with告诉它如何组合您的结果。这里的缺点是您需要再次编写完整的属性列表。
  • 您需要提供一个空的to_create块,以便它不会尝试调用array.save!之后。

1

我使用了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

寻找更好的东西


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接