Entschuldigung für den vagen Titel, es gibt viele bewegliche Teile zu diesem Problem, so denke ich, dass es erst klar wird, nachdem ich meinen Code gesehen habe. Ich bin ziemlich sicher, dass ich weiß, was hier vor sich geht und bin auf der Suche nach Feedback darüber, wie es anders zu machen:FactoryGirl-Attribut, das nach (: create) gesetzt wurde, bleibt bestehen, bis es referenziert wird?
ich ein User-Modell haben, das eine UUID attr über ein Active Rückruf setzt (das ist tatsächlich in einem " SetsUuid“Besorgnis, aber die Wirkung ist dies):
class User < ActiveRecord::Base
before_validation :set_uuid, on: :create
validates :uuid, presence: true, uniqueness: true
private
def set_uuid
self.uuid = SecureRandom.uuid
end
end
ich bin einen funktionellen rspec Controller Test für ein Schreiben von "foo/add_user" Endpunkt. Der Controller Code sieht wie folgt aus (es gibt einige andere Sachen wie Fehlerbehandlung und @foo und @params durch Filter gesetzt werden, aber Sie erhalten den Punkt. Ich weiß, das ist alles funktioniert.)
class FoosController < ApplicationController
def add_user
@foo.users << User.find_by_uuid!(@params[:user_id])
render json: {
status: 'awesome controller great job'
}
end
end
Ich schreibe ein funktionaler rspec controller test für den fall "foo/add_user fügt user zu foo hinzu". Mein Test sieht in etwa so aus (auch hier sind Dinge hier draußen, aber der Punkt sollte offensichtlich sein, und ich weiß, dass alles wie beabsichtigt funktioniert. Außerdem, um die Kommentare vorwegzunehmen: Nein, ich bin nicht wirklich "hartcodieren" den "Benutzer -uuid“String-Wert im Test, ist dies nur für das Beispiel)
RSpec.describe FoosController, type: :controller do
describe '#add_user' do
it_behaves_like 'has @foo' do
it_behaves_like 'has @params', {user_id: 'user-uuid'} do
context 'user with uuid exists' do
let(:user) { create(:user_with_uuid, uuid: params[:user_id]) } # params is set by the 'has @params' shared_context
it 'adds user with uuid to @foo' do
route.call() # route is defined by a previous let that I truncated from this example code
expect(foo.users).to include(user) # foo is set by the 'has @foo' shared_context
end
end
end
end
end
end
Und hier ist mein Benutzer Fabrik (ich habe versucht, die uUID auf verschiedene Weise einstellen, aber mein Problem (das ich in unten) ist immer die gleiche ich denke, auf diese Weise (mit Merkmalen) die eleganteste ist, so was ist das ich hier setzen).
FactoryGirl.define do
factory :user do
email { |n| "user-#{n}@example.com" }
first_name 'john'
last_name 'naglick'
phone '718-555-1234'
trait :with_uuid do
after(:create) do |user, eval|
user.update!(uuid: eval.uuid)
end
end
factory :user_with_uuid, traits: [:with_uuid]
end
end
Schließlich das Problem:
Dies funktioniert nur, wenn ich vor route.call() in der Spezifikation user.uuid referenziere.
Wie in, wenn ich einfach die Zeile "user.uuid" vor route.call() hinzufügen, funktioniert alles wie vorgesehen.
Wenn ich nicht diese Zeile haben, schlägt die Spezifikation, weil die UUID des Benutzers tatsächlich nicht durch die nach aktualisiert werden (: create) Rückruf in der Eigenschaft in der Fabrik und damit die User.find_by_uuid! Zeile im Controller findet den Benutzer nicht.
Und nur um einen anderen Kommentar vorwegzunehmen: Ich frage nicht, wie man diese Spezifikation neu schreiben, so dass es funktioniert, wie ich will. Ich weiß schon eine Vielzahl von Möglichkeiten, um dies (die einfachste und offensichtlichste ist manuell zu aktualisieren user.uuid in der Spezifikation selbst und vergessen Sie die UUID in der Fabrik insgesamt über die Einstellung) zu tun. Die Frage, die ich hier stelle, ist Warum verhält sich Factorygirl so?
Ich weiß, dass es etwas mit Lazy-Attributen zu tun hat (offensichtlich durch die Tatsache, dass es magisch funktioniert, wenn ich eine Zeile habe, die user.uuid auswertet), aber warum? Und noch besser: ist es eine Möglichkeit, die ich tun kann, was ich hier will (die UUID in der Werkseinstellung) und hat alles funktioniert, wie ich die Absicht? Ich denke, es ist eine ziemlich elegant aussehende Verwendung von rspec/factorygirl, also würde ich wirklich gerne so arbeiten.
Dank für meine lange Frage beim Lesen! Sehr schätzt nicht nur die Einsicht
es hier nicht Fabrik Mädchen, es ist rspec des 'let' - wenn Sie rufen' 'lassen es zwingt das Objekt erstellt werden/anhielt. – Anthony
@Anthony wow, danke! Das behebt mein Problem. Könntest du deine Antwort als echte Antwort posten, damit ich sie annehmen kann? Ich habe nicht darüber nachgedacht, wie Dinge in let() nicht aufgerufen werden, bis sie referenziert sind. So arbeitete ich unter dem falschen Eindruck, dass create() das Objekt immer sofort erstellen würde, obwohl es in einem let() ist. Der Grund, warum mein Code nicht mit let() funktioniert, liegt darin, dass das Benutzerobjekt erst nach der route.call() erstellt wird, zu welchem Zeitpunkt die Controller-Aktion bereits ausgeführt wurde, also find_by_uuid! findet immer nichts. –