Wie testen Sie eine Methode für ein ActiveRecord :: Relation-Objekt in rspec?

8

Wie teste ich eine Methode, die nur für eine ActiveRecord-Relationsproxy-Klasse in rspec verfügbar ist? Wie zum Beispiel sum , was so aussehen würde wie @collection.sum(:attribute)

Hier ist, was ich versuche zu tun:

%Vor%

Dies funktioniert nicht, da @invoice.line_items ein reguläres Array zurückgibt, das sum nicht auf die gleiche Weise wie ein ActiveRecord :: Relation-Objekt definiert.

Jede Hilfe wird sehr geschätzt.

    
BlissOfBeing 08.05.2014, 08:21
quelle

1 Antwort

13

Ich bin mir nicht sicher, auf welcher Rails Sie sind, deshalb werde ich in diesem Beispiel Rails 4.0.x verwenden; Das Prinzip gilt immer noch für Rails 3.x.

TL; DR: Sie möchten diesen Weg nicht nehmen.

  • Erwägen Sie nicht, Modellspezifikationen zu stubben
  • Sie können domänenspezifische APIs hinzufügen

Du gehst schnell die Straße entlang, um zu spotten. Ich bin diesen Weg gegangen, es führt nicht zum Spaß. Ein Teil von all dem liegt daran, das Gesetz von Demeter zu verletzen. Ein Teil davon besteht darin, die Rails-APIs zu verwenden, anstatt eigene Domänen-APIs zu erstellen.

Wenn Sie eine Beziehungssammlung von einem ActiveRecord -Modell anfordern, gibt es keine Array zurück, wie Sie wissen. In Rails 4.0.x mit einer Assoziation has_many lautet die zurückgegebene Klasse: ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Model .

Problem # 1: Stubbing der falsche Rückgabewert

Hier ist Ihr Rückgabetyp Array . Der tatsächliche Rückgabetyp ist ActiveRecord_Associations_CollectionProxy_Model . In Stub / Mock Land ist dies nicht unbedingt eine schlechte Sache. Wenn Sie jedoch andere Aufrufe für das vom Stub zurückgegebene Objekt verwenden möchten, müssen sie denselben API-Verträgen entsprechen. Andernfalls stempelst du nicht das gleiche Verhalten.

In diesem Fall führt die sum -Methode, die auf dem AR-Zuordnungs-Proxy definiert ist, SQL tatsächlich aus, wenn es ausgeführt wird. Die sum -Methode, die für Array definiert wurde, wird über den Active Support gepatcht. Das Verhalten Array#sum unterscheidet sich grundlegend:

%Vor%

Wie Sie sehen können, werden die Elemente summiert, nicht die Summe des angeforderten Attributs.

Problem # 2: Bestätigen Sie Ihr stub'd-Objekt

Das andere Hauptproblem, das du hast, ist, dass du versuchst, zu spezifizieren, dass du Stub bist, was du stubst. Das ergibt keinen Sinn. Der Punkt eines Stubs besteht darin, eine vordefinierte Antwort zurückzugeben. Es ist nicht zu behaupten, wie es sich verhält.

Was Sie geschrieben haben, unterscheidet sich nicht grundlegend von:

%Vor%

Sofern dies keine Plausibilitätsprüfung ist, fügt es Ihren Spezifikationen keinen wirklichen Wert hinzu.

Vorschläge

Ich bin mir nicht sicher, welche Art von Spezifikationen Sie hier schreiben. Wenn dies ein traditioneller Unit Test oder ein Akzeptanztest ist, würde ich wahrscheinlich nichts stubben. Es ist nicht unbedingt etwas falsch, wenn man eine Datenbank zeitweise schlägt, besonders wenn das, was Sie testen, ist, wie Sie damit umgehen; Das ist wirklich was du hier machst.

Sie können auch damit beginnen, Ihre eigenen spezifischen Domänenmodell-APIs zu erstellen. All dies bedeutet, dass Sie Schnittstellen für Objekte definieren, die für Ihre Domäne sinnvoll sind und die möglicherweise von einer Datenbank oder einer anderen Ressource unterstützt werden.

Nehmen Sie zum Beispiel Ihr invoice.line_items.sum(:cost).should eq(10) , dies testet eindeutig die Rails AR API. Unter Domain-Begriffen bedeutet das eigentlich nichts. % Co_de% bedeutet jedoch wahrscheinlich viel mehr für Ihre Domain:

%Vor%

Wenn Sie später invoice.subtotal in einem anderen Teil Ihres Codes verwenden, können Sie dies leicht stubeln, wenn Sie Folgendes benötigen:

%Vor%

Also, wenn es ok ist, Modellspezifikationen zu stubben? Nun, das ist wirklich ein Urteilsspruch, und wird von Mensch zu Mensch und Code-Basis zu Code-Basis variieren. Nur weil etwas in Invoice steht, bedeutet das nicht, dass es sich um ein ActiveRecord-Modell handeln muss. In diesen Fällen ist es möglicherweise sinnvoll, Domänen-APIs für Mitarbeiter auszugeben.

BEARBEITEN: app/models vs create

Im obigen Beispiel habe ich build und create(:invoice) verwendet. Wenn Sie jedoch an der Langsamkeit von DB interessiert sind, könnten Sie wahrscheinlich auch invoice.line_items.create(cost: cost) und build(:invoice) verwenden.

Beachten Sie, dass meine Verwendung von invoice.line_items.build(cost: cost) und create(:invoice) hier auf generische "factories" verweist, nicht auf einen bestimmten Edelstein. Sie könnten einfach build(:invoice) und Model.create an ihrer Stelle verwenden. Zusätzlich werden die Model.new und line_items.create von AR bereitgestellt und haben nichts mit Fabriksteinen zu tun.

    
Aaron K 08.05.2014, 17:06
quelle