Prueba de módulos en rspec

¿Cuáles son las mejores prácticas en los módulos de prueba en rspec? Tengo algunos módulos que se incluyen en algunos modelos y por ahora simplemente tengo duplicados para cada modelo (con pocas diferencias). ¿Hay alguna manera de SECARLO?

El rad way =>

let(:dummy_class) { Class.new { include ModuleToBeTested } } 

Alternativamente, puede extender la clase de prueba con su módulo:

 let(:dummy_class) { Class.new { extend ModuleToBeTested } } 

Usar ‘let’ es mejor que usar una variable de instancia para definir la clase ficticia en el before (: each)

Cuándo usar RSpec let ()?

Qué dijo mike. Aquí hay un ejemplo trivial:

código del módulo …

 module Say def hello "hello" end end 

fragmento de especificación …

 class DummyClass end before(:each) do @dummy_class = DummyClass.new @dummy_class.extend(Say) end it "get hello string" do expect(@dummy_class.hello).to eq "hello" end 

Para los módulos que se pueden probar de forma aislada o burlándose de la clase, me gusta algo como:

módulo:

 module MyModule def hallo "hallo" end end 

especulación:

 describe MyModule do include MyModule it { hallo.should == "hallo" } end 

Puede parecer incorrecto secuestrar grupos de ejemplos nesteds, pero me gusta la concisión. ¿Alguna idea?

Encontré una mejor solución en la página principal de rspec. Aparentemente es compatible con grupos de ejemplos compartidos. ¡De https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !

Grupos de ejemplos compartidos

Puede crear grupos de ejemplo compartidos e incluir esos grupos en otros grupos.

Supongamos que tiene un comportamiento que se aplica a todas las ediciones de su producto, grandes y pequeñas.

Primero, factorizar el comportamiento “compartido”:

 shared_examples_for "all editions" do it "should behave like all editions" do end end 

luego, cuando necesite definir el comportamiento para las ediciones Grande y Pequeña, haga referencia al comportamiento compartido utilizando el método it_should_behave_like ().

 describe "SmallEdition" do it_should_behave_like "all editions" it "should also behave like a small edition" do end end 

Fuera de la cabeza, ¿podrías crear una clase ficticia en tu script de prueba e incluir el módulo en eso? Luego prueba que la clase ficticia tiene el comportamiento en la forma esperada.

EDITAR: Si, como se señala en los comentarios, el módulo espera que algunos comportamientos estén presentes en la clase en la que está mezclado, entonces trataría de implementar variables ficticias de esos comportamientos. Solo lo suficiente para que el módulo esté feliz de realizar sus tareas.

Dicho esto, estaría un poco nervioso acerca de mi diseño cuando un módulo espera mucho de su clase de host (¿diremos “host”?) – Si aún no he heredado de una clase base o no puedo inyectar la nueva funcionalidad en el árbol de herencia, entonces creo que estaría tratando de minimizar las expectativas que un módulo pueda tener. Mi preocupación es que mi diseño podría comenzar a desarrollar algunas áreas de inflexibilidad desagradable.

La respuesta aceptada es la correcta, creo, pero quería agregar un ejemplo de cómo usar los métodos shared_examples_for y it_behaves_like . Menciono algunos trucos en el fragmento de código, pero para obtener más información, vea esta guía de relishapp-rspec .

Con esto puedes probar tu módulo en cualquiera de las clases que lo incluyen. Entonces realmente estás probando lo que usas en tu aplicación.

Veamos un ejemplo:

 # Lets assume a Movable module module Movable def self.movable_class? true end def has_feets? true end end # Include Movable into Person and Animal class Person < ActiveRecord::Base include Movable end class Animal < ActiveRecord::Base include Movable end 

Ahora movable_spec.rb crear especificaciones para nuestro módulo: movable_spec.rb

 shared_examples_for Movable do context 'with an instance' do before(:each) do # described_class points on the class, if you need an instance of it: @obj = described_class.new # or you can use a parameter see below Animal test @obj = obj if obj.present? end it 'should have feets' do @obj.has_feets?.should be_true end end context 'class methods' do it 'should be a movable class' do described_class.movable_class?.should be_true end end end # Now list every model in your app to test them properly describe Person do it_behaves_like Movable end describe Animal do it_behaves_like Movable do let(:obj) { Animal.new({ :name => 'capybara' }) } end end 

Qué pasa:

 describe MyModule do subject { Object.new.extend(MyModule) } it "does stuff" do expect(subject.does_stuff?).to be_true end end 

Yo sugeriría que para los módulos más grandes y más usados ​​uno debería optar por los “Grupos de ejemplos compartidos” como lo sugiere @Andrius aquí . Para cosas sencillas para las que no quiere pasar por el problema de tener varios archivos, etc., aquí se explica cómo garantizar el máximo control sobre la visibilidad de su material ficticio (probado con rspec 2.14.6, simplemente copie y pegue el código en un archivo de especificaciones y ejecútelo):

 module YourCoolModule def your_cool_module_method end end describe YourCoolModule do context "cntxt1" do let(:dummy_class) do Class.new do include YourCoolModule #Say, how your module works might depend on the return value of to_s for #the extending instances and you want to test this. You could of course #just mock/stub, but since you so conveniently have the class def here #you might be tempted to use it? def to_s "dummy" end #In case your module would happen to depend on the class having a name #you can simulate that behaviour easily. def self.name "DummyClass" end end end context "instances" do subject { dummy_class.new } it { subject.should be_an_instance_of(dummy_class) } it { should respond_to(:your_cool_module_method)} it { should be_a(YourCoolModule) } its (:to_s) { should eq("dummy") } end context "classes" do subject { dummy_class } it { should be_an_instance_of(Class) } it { defined?(DummyClass).should be_nil } its (:name) { should eq("DummyClass") } end end context "cntxt2" do it "should not be possible to access let methods from anohter context" do defined?(dummy_class).should be_nil end end it "should not be possible to access let methods from a child context" do defined?(dummy_class).should be_nil end end #You could also try to benefit from implicit subject using the descbie #method in conjunction with local variables. You may want to scope your local #variables. You can't use context here, because that can only be done inside #a describe block, however you can use Porc.new and call it immediately or a #describe blocks inside a describe block. #Proc.new do describe "YourCoolModule" do #But you mustn't refer to the module by the #constant itself, because if you do, it seems you can't reset what your #describing in inner scopes, so don't forget the quotes. dummy_class = Class.new { include YourCoolModule } #Now we can benefit from the implicit subject (being an instance of the #class whenever we are describing a class) and just.. describe dummy_class do it { should respond_to(:your_cool_module_method) } it { should_not be_an_instance_of(Class) } it { should be_an_instance_of(dummy_class) } it { should be_a(YourCoolModule) } end describe Object do it { should_not respond_to(:your_cool_module_method) } it { should_not be_an_instance_of(Class) } it { should_not be_an_instance_of(dummy_class) } it { should be_an_instance_of(Object) } it { should_not be_a(YourCoolModule) } end #end.call end #In this simple case there's necessarily no need for a variable at all.. describe Class.new { include YourCoolModule } do it { should respond_to(:your_cool_module_method) } it { should_not be_a(Class) } it { should be_a(YourCoolModule) } end describe "dummy_class not defined" do it { defined?(dummy_class).should be_nil } end 

mi trabajo reciente, utilizando el menor cableado posible

 require 'spec_helper' describe Module::UnderTest do subject {Object.new.extend(described_class)} context '.module_method' do it {is_expected.to respond_to(:module_method)} # etc etc end end 

Deseo

 subject {Class.new{include described_class}.new} 

funcionó, pero no (como en Ruby MRI 2.2.3 y RSpec :: Core 3.3.0)

 Failure/Error: subject {Class.new{include described_class}.new} NameError: undefined local variable or method `described_class' for # 

Obviamente, la clase descrita no está visible en ese scope.

También puedes usar el tipo de ayuda

 # api_helper.rb module Api def my_meth 10 end end 
 # spec/api_spec.rb require "api_helper" RSpec.describe Api, :type => :helper do describe "#my_meth" do it { expect( helper.my_meth ).to eq 10 } end end 

Aquí está la documentación: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec

simplemente debe incluir su módulo en su archivo de especificaciones mudule Test module MyModule def test 'test' end end end en su archivo spec RSpec.describe Test::MyModule do include Test::MyModule #you can call directly the method *test* it 'returns test' do expect(test).to eql('test') end end

Una posible solución para probar el método del módulo que son independientes en la clase que los incluirá

 module moduleToTest def method_to_test 'value' end end 

Y especificaciones para eso

 describe moduleToTest do let(:dummy_class) { Class.new { include moduleToTest } } let(:subject) { dummy_class.new } describe '#method_to_test' do it 'returns value' do expect(subject.method_to_test).to eq('value') end end end 

Y si quieres DRY probarlos, entonces shared_examples es un buen enfoque