15

Ho un modello ActiveRecord, PricePackage. Questo ha un richiamo before_create. Questa richiamata utilizza un'API di terze parti per stabilire una connessione remota. Sto usando una ragazza di fabbrica e vorrei eliminare questa API in modo che quando vengono create nuove fabbriche durante il test, le chiamate remote non vengano effettuate.Come eseguire il mock e lo stub record attivo before_create callback con factory_girl

Sto usando Rspec per mock e stub. Il problema che sto avendo è che i metodi RSpec non sono disponibili nel mio factories.rb

modello:

class PricePackage < ActiveRecord::Base 
    has_many :users 
    before_create :register_with_3rdparty 

    attr_accessible :price, :price_in_dollars, :price_in_cents, :title 


    def register_with_3rdparty 
     return true if self.price.nil? 

     begin 
      3rdPartyClass::Plan.create(
      :amount => self.price_in_cents, 
      :interval => 'month', 
      :name => "#{::Rails.env} Item #{self.title}", 
      :currency => 'usd', 
      :id => self.title) 
     rescue Exception => ex 
      puts "stripe exception #{self.title} #{ex}, using existing price" 
      plan = 3rdPartyClass::Plan.retrieve(self.title) 
      self.price_in_cents = plan.amount 
      return true 
     end 
    end 

fabbrica:

#PricePackage 
Factory.define :price_package do |f| 
    f.title "test_package" 
    f.price_in_cents "500" 
    f.max_domains "20" 
    f.max_users "4" 
    f.max_apps "10" 
    f.after_build do |pp| 
    # 
    #heres where would like to mock out the 3rd party response 
    # 
    3rd_party = mock() 
    3rd_party.stub!(:amount).price_in_cents 
    3rdPartyClass::Plan.stub!(:create).and_return(3rd_party) 
    end 
end 

non sono sicuro di come ottenere il rspec mock e stub helper caricati nel mio factories.rb e questo potrebbe non essere il modo migliore per gestirlo.

+1

per inciso, quando si assegna una taglia a una domanda che taglia sarà preso dal tuo reputazione indipendentemente dal fatto che si assegna quindi è una buona cosa da fare a f seguitela e assegnala ad una delle risposte che le persone danno. Senza farlo evapora semplicemente –

+0

Does 'pp.stub (: register_with_3rdparty) {true}' in 'after_build' solleva errori? – lulalala

risposta

19

Come autore della gemma VCR, probabilmente ti aspetteresti di consigliarlo per casi come questi.Lo raccomando per testare il codice dipendente da HTTP, ma penso che ci sia un problema di fondo con il tuo design. Non dimenticare che TDD (sviluppo basato sui test) è pensato per essere una disciplina di progettazione, e quando trovi doloroso provare facilmente qualcosa, questo ti sta dicendo qualcosa sul tuo design. Ascolta il dolore dei tuoi test!

In questo caso, penso che il tuo modello non abbia attività che fanno la chiamata API di terze parti. È una violazione piuttosto significativa del principio di responsabilità singola. I modelli dovrebbero essere responsabili per la convalida e la persistenza di alcuni dati, ma questo è decisamente oltre.

Invece, ti consiglio di spostare la chiamata API di terze parti in un osservatore. Pat Maddox ha un great blog post che discute su come gli osservatori possono (e dovrebbero) essere usati per accoppiare in modo approssimativo le cose senza violare l'SRP (principio di responsabilità singola), e come questo rende il test molto più semplice e migliora anche la progettazione.

Dopo averlo spostato in un osservatore, è abbastanza semplice disabilitare l'osservatore nei test dell'unità (tranne per i test specifici per quell'osservatore), ma tenerlo abilitato in produzione e nei test di integrazione. Puoi utilizzare il plug-in Pat's no-peeping-toms per aiutarti con questo, oppure, se sei su rails 3.1, dovresti controllare lo new functionality integrato in ActiveModel che ti consente di effettuare il easily enable/disable observers.

+0

Cosa se la chiamata API * è * la persistenza (un po 'come fa ActiveResource)? Sembra inappropriato che * non * sia nel modello. –

+0

Certo, se i dati vengono mantenuti su un'API HTTP, allora sì, questa sarebbe la responsabilità principale del modello, e dovrebbe essere assolutamente nel modello (o nella superclasse o nel modulo mescolato nel modello). Si noti che ho detto "Chiamata API di terze parti". Non considererei mai la tua persistenza come un'API di 3a parte. –

+0

Giusto, ho capito il tuo punto. Grazie per il chiarimento. –

-1

Beh, in primo luogo, hai ragione che 'finta e stub' non sono il linguaggio di Factory Girl

Indovinando i vostri rapporti modello, penso che si vorrà costruire un altro stabilimento oggetto, impostare le sue proprietà e quindi associarli.

#PricePackage 
Factory.define :price_package do |f| 
    f.title "test_package" 
    f.price_in_cents "500" 
    f.max_domains "20" 
    f.max_users "4" 
    f.max_apps "10" 
    f.after_build do |pp| 
    f.3rdClass { Factory(:3rd_party) } 
end 

Factory.define :3rd_party do |tp| 
    tp.price_in_cents = 1000 
end 

Speriamo di non alterare la relazione in modo illeggibile.

+0

Non esiste un'associazione dati tra 'price_package' e le terze parti. Ho aggiunto un esempio del mio modello. Il che aiuta a dimostrare che l'API di 3rdparty è chiamata all'interno del callback 'before_create' di rails. Quindi voglio stubare e deridere quella parte all'interno del metodo register_with_3rdparty. In questo modo, la ragazza della fabbrica non si connette direttamente ogni volta che viene creato un nuovo stabilimento "price_package". – kevzettler

1

Controlla la gemma VCR (https://www.relishapp.com/myronmarston/vcr). Registra le interazioni HTTP della tua suite di test e le riproduce per te. Rimozione di qualsiasi requisito per effettuare effettivamente connessioni HTTP a API di terze parti. Ho trovato che questo è un approccio molto più semplice di quello che deride l'interazione manualmente. Ecco un esempio utilizzando una libreria Foursquare.

VCR.config do |c| 
    c.cassette_library_dir = 'test/cassettes' 
    c.stub_with :faraday 
end 

describe Checkin do 
    it 'must check you in to a location' do 
    VCR.use_cassette('foursquare_checkin') do 
     Skittles.checkin('abcd1234') # Doesn't actually make any HTTP calls. 
            # Just plays back the foursquare_checkin VCR 
            # cassette. 
    end 
    end 
end 
0

factorygirl può spegnere attributi di un oggetto, forse che può aiutare:

# Returns an object with all defined attributes stubbed out 
stub = FactoryGirl.build_stubbed(:user) 

è possibile trovare maggiori informazioni in FactoryGirl's rdocs

1

Anche se posso vedere l'appello in termini di incapsulamento, lo stubing di terze parti non deve necessariamente accadere (e in qualche modo forse non dovrebbe accadere) all'interno della vostra fabbrica.

Invece di incapsularlo in fabbrica, è sufficiente definirlo all'inizio dei test RSpec. Ciò garantisce che le ipotesi dei test siano chiare e dichiarate all'inizio (che può essere molto utile durante il debug)

Prima di qualsiasi test che utilizza PricePlan, impostare la risposta desiderata e quindi restituirla alla terza parte .create metodo:

before(:all) do 
    3rd_party = mock('ThirdParty') 
    3rdPartyClass::Plan.stub(:create).and_return(true) 
end 

Questo dovrebbe consentire di chiamare il metodo ma effettuerà la chiamata remota.

* Sembra che lo stub di terze parti abbia alcune dipendenze dall'oggetto originale (: price_in_cents) tuttavia senza sapere più della dipendenza esatta non riesco a indovinare quale sarebbe lo stub appropriato (o se ne è necessario uno qualsiasi) *

+0

non sembra funzionare: TypeError: # non è una classe/modulo – avioing

+0

ecco un altro problema con questo approccio ... normalmente, accetterei con tutto il cuore WRT mantenendo prova pulito in questo caso, tuttavia, poiché è probabile che il modello venga utilizzato, direttamente o indirettamente, in molte dozzine di test, dovresti farlo in ogni singolo test ... quindi @kevzettler cerca l'incapsulamento (all'interno della fabbrica) – avioing

0

Ho avuto lo stesso esatto problema. discussione Observer da parte (potrebbe essere il destra approccio), ecco che cosa ha funzionato per me (è un inizio e può/deve essere migliorato):

aggiungere un file 3rdparty.rb a spec/supporto con questi contenuti :

RSpec.configure do |config| 
    config.before do 
    stub(3rdPartyClass::Plan).create do 
    [add stuff here] 
    end 
    end 
end 

E assicurarsi che il vostro spec_helper.rb ha questo:

Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } 
Problemi correlati