15

Tengo un modelo ActiveRecord, PricePackage. Eso tiene una llamada de vuelta before_create. Esta devolución de llamada utiliza una API de terceros para establecer una conexión remota. Estoy usando factory girl y me gustaría cerrar esta API para que cuando se construyan nuevas fábricas durante la prueba, las llamadas remotas no se realicen.Cómo simular y resguardar el registro activo before_create callback with factory_girl

Estoy usando Rspec para burlas y trozos. El problema que estoy teniendo es que los métodos RSpec no están disponibles dentro de mi factories.rb

modelo:

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 

fábrica:

#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 

No estoy seguro de cómo obtener el rspec mock y stub helpers cargados en mis factories.rb y esta podría no ser la mejor manera de manejar esto.

+1

Como acotación al margen, cuando se asigna una recompensa a una pregunta que recompensa será tomado de su reputación, independientemente de si se asigna así que es algo bueno que hacer para f ahórrelo y asócielo a una de las respuestas que da la gente. Sin hacerlo, simplemente se evapora –

+0

¿'pp.stub (: register_with_3rdparty) {true}' en 'after_build' provoca algún error? – lulalala

Respuesta

19

Como el autor de la gema de VCR, probablemente esperarías que lo recomendara para casos como estos.De hecho, lo recomiendo para probar el código dependiente de HTTP, pero creo que hay un problema subyacente con su diseño. No olvide que TDD (desarrollo basado en pruebas) pretende ser una disciplina de diseño, y cuando le resulta doloroso probar fácilmente algo, eso le dice algo acerca de su diseño. ¡Escucha el dolor de tus exámenes!

En este caso, creo que su modelo no tiene nada que hacer al API de terceros. Es una violación bastante significativa del principio de responsabilidad única. Los modelos deberían ser responsables de la validación y persistencia de algunos datos, pero esto definitivamente va más allá.

En su lugar, le recomendaría que cambie la llamada de la API de terceros a un observador. Pat Maddox tiene un great blog post discutiendo cómo los observadores pueden (y deberían) ser utilizados para acoplar cosas sin violar el SRP (principio de responsabilidad única), y cómo eso hace que las pruebas sean mucho más fáciles y también mejora su diseño.

Una vez que ha trasladado eso a un observador, es bastante fácil desactivar el observador en las pruebas de su unidad (excepto las pruebas específicas para ese observador), pero mantenerlo habilitado en producción y en sus pruebas de integración. Puede usar el complemento no-peeping-toms de Pat para ayudar con esto, o, si está en los raíles 3.1, debe verificar el new functionality integrado en ActiveModel que le permite easily enable/disable observers.

+0

Qué si la llamada a la API * es * la persistencia (algo así como lo que hace ActiveResource)? Parece inapropiado que * no * esté en el modelo. –

+0

Claro, si los datos se persisten a través de una API HTTP, entonces sí, esa sería la responsabilidad principal del modelo, y debería estar absolutamente en el modelo (o en superclase o módulo mezclado en el modelo). Tenga en cuenta que dije "llamada API de terceros". Nunca consideraría tu persistencia como una tercera parte API. –

+0

Bien, entiendo su punto. Gracias por la aclaración. –

-1

Bueno, en primer lugar, tienes razón en que 'se burlan y talón' no son el lenguaje de la fábrica de chicas

Las conjeturas sobre sus relaciones de modelos, pienso que usted quiere construir otra fábrica objeto, establecer sus propiedades , y luego asócialos.

#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 

Espero que no mangle la relación ilegiblemente.

+0

No hay asociación de datos entre 'price_package' y cosas de terceros. Agregué un ejemplo de mi modelo. Lo que ayuda a demostrar que la API de terceros se llama dentro de la devolución de llamada 'before_create'. Así que quiero rescindir y simular esa parte dentro del método register_with_3rdparty. De modo que esa chica de la fábrica no se conecta directamente cada vez que se crea una nueva fábrica de 'paquete de precio'. – kevzettler

1

Pagar la moneda de VCR (https://www.relishapp.com/myronmarston/vcr). Grabará las interacciones HTTP de su suite de pruebas y las reproducirá por usted. Eliminando cualquier requisito para hacer realmente conexiones HTTP a API de terceros. He encontrado que este es un enfoque mucho más simple que burlar la interacción de forma manual. Aquí hay un ejemplo usando una biblioteca 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 puede apagar atributos de un objeto, tal que puede ayudarle a:

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

usted puede encontrar más información en FactoryGirl's rdocs

1

Aunque puedo ver el atractivo en términos de encapsulación, el trozo de terceros no tiene que suceder (y de alguna manera tal vez no debería suceder) dentro de su fábrica.

En lugar de encapsularlo en la fábrica, simplemente puede definirlo al inicio de las pruebas de RSpec. Hacer esto también asegura que las suposiciones de sus pruebas sean claras y establecidas al inicio (lo cual puede ser muy útil al depurar)

Antes de cualquier prueba que use PricePlan, configure la respuesta deseada y luego devuélvala al tercero .create método:

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

Esto debería permitirle llamar al método pero evitará la llamada remota.

* Parece que su stub de 3rd Party tiene algunas dependencias con el objeto original (: price_in_cents) sin embargo, sin saber más acerca de la dependencia exacta no puedo adivinar cuál sería el stubbing apropiado (o si es necesario) *

+0

no parece estar funcionando: TypeError: # no es una clase/módulo – avioing

+0

aquí hay otro problema con este enfoque ... normalmente, estaría totalmente de acuerdo WRT manteniendo el prueba limpia en este caso, sin embargo, dado que es probable que el modelo se use -directa o indirectamente- en muchas docenas de pruebas, tendrías que hacer esto en cada prueba ... por lo tanto, @kevzettler busca la encapsulación (dentro de la fábrica) – avioing

0

Tuve el mismo problema exacto. discusión de observador a un lado (que podría ser el enfoque correcto ), aquí es lo que funcionó para mí (es un comienzo y puede/debe ser mejorado):

añadir un archivo 3rdparty.rb a las especificaciones/soporte con estos contenidos :

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

Y asegúrese de que su spec_helper.rb tiene esta:

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