Wie definiere ich ein Array / einen Hash in factory_bot?

80

Ich versuche, einen Test zu schreiben, der einige Rückgabewerte vom REST-Service von Dropbox simuliert, der mir Daten in einem Array mit einem verschachtelten Hash zurückgibt.

Ich habe Probleme herauszufinden, wie ich meine Factory codieren soll, da das Rückgabeergebnis ein Array mit einem Inneren ist. Was würde hier gehen?

Factory.define :dropbox_hash do
 ??
end

Dropbox-Daten sehen folgendermaßen aus:

 ["/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"}] 

Und ich hätte gerne einen Werksaufruf wie diesen in meiner RSpec:

Factory.create(:dropbox_hash)
Doug
quelle
Benötigen Sie dafür wirklich eine Fabrik? Warum nicht einfach eine Methode definieren, die die simulierte Antwort zurückgibt?
Zetetic
Das habe ich letztendlich getan. Aber ich dachte, der Sinn der Fabriken wäre es, dieses Zeug zu isolieren. Ich bin immer noch neugierig - anscheinend sind Hash und Array Klassen und dies sollte funktionieren, wenn ich nur die richtige Syntax bekomme.
Doug
Ich habe sie nur zum Generieren von ActiveRecord-Modellinstanzen verwendet. FactoryGirl soll Vorrichtungen ersetzen. Vielleicht haben Sie einen Blick auf RSpec Helfer Methoden nehmen: relishapp.com/rspec/rspec-core/v/2-9/docs/helper-methods
zetetic

Antworten:

152

Ich war daran interessiert, dasselbe zu tun und auch ein Modell von mir zu testen, das mit einem Hash von Inhalten aus einer Drittanbieter-API arbeitet. Ich fand heraus, dass ich mithilfe einiger der integrierten Funktionen von factory_girl diese Art von Datenstrukturen sauber erstellen konnte.

Hier ist ein erfundenes Beispiel:

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

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

    initialize_with { attributes } 
  end

Der Haupttrick hier ist, dass factory_girl beim Deklarieren von initialize_with nicht mehr versucht, die Attribute dem resultierenden Objekt zuzuweisen. In diesem Fall scheint es auch den Datenbankspeicher zu überspringen. Anstatt etwas Kompliziertes zu konstruieren, geben wir einfach den bereits vorbereiteten Attribut-Hash als unseren Inhalt zurück. Voila.

Es scheint notwendig, einen Wert für die Klasse anzugeben, obwohl dieser nicht tatsächlich verwendet wird. Dies soll verhindern, dass factory_girl versucht, eine Klasse basierend auf dem Factory-Namen zu instanziieren. Ich habe mich dafür entschieden, beschreibende Klassen anstelle von Object zu verwenden, aber es liegt an Ihnen.

Sie können weiterhin Felder überschreiben, wenn Sie eine dieser Hash-Fabriken verwenden:

chick = FactoryGirl.build(:chicken, name:"Charles")

Wenn Sie jedoch verschachtelten Inhalt haben und tiefere Felder überschreiben möchten, müssen Sie die Komplexität des Initialisierungsblocks erhöhen, um eine Art Deep Merge durchzuführen.

In Ihrem Fall verwenden Sie gemischte Array- und Hash-Daten, und es scheint, dass die Path-Eigenschaft zwischen Teilen der Datenstruktur wiederverwendet werden sollte. Kein Problem - Sie kennen die Struktur des Inhalts, sodass Sie einfach eine Factory erstellen können, die das resultierende Array ordnungsgemäß erstellt. So könnte ich es machen:

  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)

Sie können auch weiterhin unnötige Werte weglassen. Stellen wir uns vor, nur Pfad und Drehzahl sind wirklich notwendig:

  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)
doppelt markiert
quelle
Dies ist eine großartige Lösung. Es funktioniert sogar mit der Vererbung von Fabriken.
David Pelaez
Dies funktioniert für mich zu erstellen / bauen, aber es versagt die Flusen: FacoryGirl.lint
Pixelearth
Danke dafür. Um den Rubocop-Linter im favoritesmehrzeiligen Block zu übergeben, verwenden Sie do..endstattdessen anstelle von {{..}}.
Scarver2
Sie können skip_createzur Fabrik hinzufügen , wenn Sie "erstellen" anstelle vonbuild
Sairam
10

Ich habe das für mich zum Laufen gebracht, und ich kann Attribute nach Bedarf an den Hash übergeben

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" }
Jethroo
quelle
3
Dies hat auch den zusätzlichen Vorteil, dass die Schlüssel Zeichenfolgen sein können.
Crftr
Der Nachteil hierbei ist, dass Sie bei jeder Erstellung ein einzelnes und kein neues Objekt ändern. Werfen Sie einen Blick auf die object_id des Hashs
Anthony
6

Sie können dies in den neuesten Versionen von factory_girl tun, aber es ist umständlich, da es zum Erstellen von Objekten und nicht von Datenstrukturen entwickelt wurde. Hier ist ein Beispiel:

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

Ich gehe hier die seltsamen Dinge durch:

  • Jede Fabrik benötigt eine gültige Build-Klasse, auch wenn sie nicht verwendet wird. Deshalb habe ich Objecthier bestanden, um zu verhindern, dass sie gesucht wird DropboxHash.
  • Sie müssen alle Attribute mithilfe eines ignoreBlocks ignorieren , damit nicht versucht wird, sie anschließend dem Array zuzuweisen array.revision = 48.
  • Sie können angeben, wie Sie Ihr Ergebnis mithilfe von zusammenstellen möchten initialize_with. Der Nachteil hierbei ist, dass Sie die vollständige Attributliste erneut ausschreiben müssen.
  • Sie müssen einen leeren to_createBlock angeben, damit er nicht versucht, array.save!danach aufzurufen .
Joe Ferris
quelle
0

Ich habe OpenStruct verwendet:

factory :factory_hash, class:OpenStruct do
  foo "bar"
  si "flar"
end

Edit: sorry, funktioniert nicht als Hash

Ich benutze endlich eine statische Version, nur um zu verhindern, dass der Hash vom Factory-System kommt ...

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

auf der Suche nach etwas Besserem

jobwat
quelle